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
30 changes: 30 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,35 @@
# Changelog

## 2.7.0

### Changed: the primary colour is blue (PROD-2966)

The primary colour changes from purple to blue-600 (`#1A56DB`). This is a visible change for every
consumer.

- **Primary buttons** (`Button`, `Capsule`, `ButtonDropdown`): fill `primary-600`, hover
`primary-700`, pressed `primary-800`, disabled `primary-300`. The loader spinner is `primary-600`.
- **Secondary buttons**: `primary-50` fill, `primary-400` border, `primary-700` text, `primary-100`
hover.
- **Checked and selected states** (`Checkbox`, `Radio`, `ToggleSwitch`, `ComboBox` options):
`primary-600`.
- **Focus**: blue-600 everywhere. Rings are `primary-600` (`Radio` was `purple-500`), and text
inputs, selects and comboboxes focus with a `primary-600` border. `TextArea` now focuses with a
single border and no ring, like the other inputs.
- `Badge` with `color="primary"` is blue. `color="secondary"` stays violet.

### Added: `primary-*` as CSS variables, and a Tailwind preset (PROD-2967)

- `primary-50` to `primary-950` are now CSS variables (`--primary-*`) in `dist/tailwind.css`, and
components use `primary-*` instead of raw `purple-*` and `violet-*` classes. `primary-50` was a
grey and `primary-950` didn't exist.
- The theme now lives in `tailwind.preset.js`, exported as `@agility/plenum-ui/tailwind.preset`, so
a consumer's own classes match Plenum's. See "Colours" in the README.

### Fixed: `blue-300`

`blue-300` was `#8FB2`, which browsers read as a nearly transparent green. It is now `#8FB2FB`.

## 2.6.3

### Fixed: `TextArea` placeholder colour
Expand Down
18 changes: 18 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,24 @@ import "@agility/plenum-ui/dist/lib/tailwind.css"

Make sure to add any additional styles before these two import statements to prevent overwriting the Plenum styling.

## Colours

The primary colour is blue-600 (`#1A56DB`). Its scale, `primary-50` to `primary-950`, is a set of CSS
variables (`--primary-50` … `--primary-950`) defined in `dist/tailwind.css`. Components use `primary-*`
for anything that means "primary" (buttons, focus, checked and selected states), never a palette name
such as `blue-*` or `purple-*`, so a future colour change is an edit to the preset, not to every
component.

To build your own Tailwind classes with the same colours, load Plenum's preset in your
`tailwind.config.js`:

```js
module.exports = {
presets: [require("@agility/plenum-ui/tailwind.preset")],
content: ["./src/**/*.{js,ts,jsx,tsx}"]
}
```

## Installation

You can install the Plenum library using npm or yarn:
Expand Down
2 changes: 1 addition & 1 deletion app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
@layer {
* {
/* set the default outline color on focus */
outline-color: #7933dd;
outline-color: rgb(var(--primary-600));
}
/* Thin - 100 */
@font-face {
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@agility/plenum-ui",
"version": "2.6.3",
"version": "2.7.0",
"license": "MIT",
"main": "dist/index.js",
"module": "dist/index.js",
Expand Down
12 changes: 6 additions & 6 deletions stories/atoms/badges/Badge.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ const Badge: React.FC<IBadgeProps> = ({
actionButton
}) => {
const badgeStyles = cn(
"text-sm flex items-center py-[2px] focus:ring-1 focus:ring-purple-600 outline-0",
"text-sm flex items-center py-[2px] focus:ring-1 focus:ring-primary-600 outline-0",
{
"rounded-[3px]": variant === "rounded",
"rounded-full px-[6px]": variant === "pill",
Expand All @@ -43,7 +43,7 @@ const Badge: React.FC<IBadgeProps> = ({
"pr-3": !removeButton && size === "lg",
"pr-[10px]": !removeButton && size === "sm",
"gap-x-[6px]": statusDot || removeButton || loading,
"bg-purple-100 text-purple-800 ": color === "primary",
"bg-primary-100 text-primary-800 ": color === "primary",
"bg-violet-100 text-violet-800": color === "secondary",
"bg-pink-100 text-pink-800": color === "pink",
"bg-red-100 text-red-800": color === "danger",
Expand All @@ -54,7 +54,7 @@ const Badge: React.FC<IBadgeProps> = ({
}
)
const statusDotStyles = cn("rounded-full h-[6px] p-[1px] w-[6px]", {
"bg-purple-400 text-purple-400": color === "primary",
"bg-primary-400 text-primary-400": color === "primary",
"bg-violet-400 text-violet-400": color === "secondary",
"bg-pink-400 text-pink-400": color === "pink",
"bg-red-400 text-red-400": color === "danger",
Expand All @@ -64,9 +64,9 @@ const Badge: React.FC<IBadgeProps> = ({
"bg-gray-400 text-gray-400": color === "basic"
})
const removeButtonStyles: ClassNameWithAutocomplete = cn(
"h-4 w-4 group-focus-within:ring-1 group-focus-within:ring-purple-600 rounded-full outline-0",
"h-4 w-4 group-focus-within:ring-1 group-focus-within:ring-primary-600 rounded-full outline-0",
{
"text-purple-400": color === "primary",
"text-primary-400": color === "primary",
"text-violet-400": color === "secondary",
"text-pink-400": color === "pink",
"text-red-400": color === "danger",
Expand All @@ -78,7 +78,7 @@ const Badge: React.FC<IBadgeProps> = ({
)

const loaderStyles = cn("h-4 w-4 rounded-full border-2 animate-spin", {
"border-purple-400 !border-r-purple-200": color === "primary",
"border-primary-400 !border-r-primary-200": color === "primary",
"border-violet-400 !border-r-violet-200": color === "secondary",
"border-pink-400 !border-r-pink-200": color === "pink",
"border-red-400 !border-r-red-200": color === "danger",
Expand Down
16 changes: 8 additions & 8 deletions stories/atoms/buttons/Button/Button.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ const _Button = (
const applyMinWidth = !!label && size !== "xs" && size !== "sm";
let iconStyles = cn(
{ "text-white h-5 w-5 stroke-[1.5]": actionType === "primary" || actionType === "danger" },
{ "text-purple-700 h-5 w-5 stroke-[1.5]": actionType === "secondary" },
{ "text-primary-700 h-5 w-5 stroke-[1.5]": actionType === "secondary" },
{ "text-gray-400 h-5 w-5 stroke-[1.5]": actionType === "alternative" },
{ "text-transparent-black-40 h-5 w-5 stroke-[1.5]": actionType === "warning" }
);
Expand All @@ -73,7 +73,7 @@ const _Button = (

const loaderColors = cn(
{ "border-r-white": actionType === "primary" },
{ "border-purple-200 border-r-purple-700": actionType === "secondary" },
{ "border-primary-200 border-r-primary-700": actionType === "secondary" },
{ "border-gray-200 border-r-gray-700": actionType === "alternative" },
{ "border-red-800 border-r-white": actionType === "danger" },
{ "border-yellow-800 border-r-transparent-black-70": actionType === "warning" }
Expand All @@ -93,7 +93,7 @@ const _Button = (
title: asLink.title,
className: twMerge(
cn(
"inline-flex items-center justify-center gap-x-2 font rounded-[3px] outline-none focus-visible:ring-1 focus-visible:ring-purple-600 transition-all",
"inline-flex items-center justify-center gap-x-2 font rounded-[3px] outline-none focus-visible:ring-1 focus-visible:ring-primary-600 transition-all",
{ "min-w-[130px]": applyMinWidth },
{ "w-full": fullWidth },
{ "px-[11px] py-[7px] text-xs": resolvedSize === "xs" },
Expand All @@ -102,11 +102,11 @@ const _Button = (
{ "px-[17px] py-[9px] text-base": resolvedSize === "lg" },
{ "px-[25px] py-[13px] text-base": resolvedSize === "xl" },
{
"bg-violet-800 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-violet-700 hover:bg-violet-700 disabled:bg-violet-400 disabled:hover:bg-violet-400 disabled:focus-visible:ring-0":
"bg-primary-600 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-primary-700 hover:bg-primary-700 disabled:bg-primary-300 disabled:hover:bg-primary-300 disabled:focus-visible:ring-0":
actionType === "primary"
},
{
" bg-purple-50 border-purple-400 border hover:border-purple-500 text-purple-700 hover:bg-purple-100 focus-within:bg-purple-100 focus-visible:bg-purple-100 focus:bg-purple-100 active:bg-purple-100 disabled:bg-purple-50 disabled:hover:bg-purple-50 disabled:text-purple-300 disabled:focus-visible:ring-0":
" bg-primary-50 border-primary-400 border hover:border-primary-500 text-primary-700 hover:bg-primary-100 focus-within:bg-primary-100 focus-visible:bg-primary-100 focus:bg-primary-100 active:bg-primary-100 disabled:bg-primary-50 disabled:hover:bg-primary-50 disabled:text-primary-300 disabled:focus-visible:ring-0":
actionType === "secondary"
},
{
Expand Down Expand Up @@ -176,7 +176,7 @@ const _Button = (
type="button"
className={twMerge(
cn(
" px-4 py-2 inline-flex items-center justify-center gap-x-2 rounded outline-none focus-visible:ring-1 focus-visible:ring-purple-600 transition-all h-9",
" px-4 py-2 inline-flex items-center justify-center gap-x-2 rounded outline-none focus-visible:ring-1 focus-visible:ring-primary-600 transition-all h-9",
{ "min-w-[130px]": applyMinWidth },
{ "w-full": fullWidth },
{ "text-xs": resolvedSize === "xs" },
Expand All @@ -185,11 +185,11 @@ const _Button = (
{ "text-base": resolvedSize === "lg" },
{ "text-base": resolvedSize === "xl" },
{
"bg-violet-800 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-violet-700 hover:bg-violet-700 disabled:bg-violet-400 disabled:hover:bg-violet-400 disabled:focus-visible:ring-0":
"bg-primary-600 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-primary-700 hover:bg-primary-700 disabled:bg-primary-300 disabled:hover:bg-primary-300 disabled:focus-visible:ring-0":
actionType === "primary"
},
{
" bg-purple-50 border-purple-400 border hover:border-purple-500 text-purple-700 hover:bg-purple-100 focus-within:bg-purple-100 focus-visible:bg-purple-100 focus:bg-purple-100 active:bg-purple-100 disabled:bg-purple-50 disabled:hover:bg-purple-50 disabled:text-purple-300 disabled:focus-visible:ring-0":
" bg-primary-50 border-primary-400 border hover:border-primary-500 text-primary-700 hover:bg-primary-100 focus-within:bg-primary-100 focus-visible:bg-primary-100 focus:bg-primary-100 active:bg-primary-100 disabled:bg-primary-50 disabled:hover:bg-primary-50 disabled:text-primary-300 disabled:focus-visible:ring-0":
actionType === "secondary"
},
{
Expand Down
4 changes: 2 additions & 2 deletions stories/atoms/buttons/Button/tests/Button.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,14 @@ describe("<Button>", () => {
render(<Button {...defaultProps} />);

const buttonElement = screen.getByRole("button");
expect(buttonElement).toHaveClass("bg-violet-800");
expect(buttonElement).toHaveClass("bg-primary-600");
});

it("renders with actionType as secondary", () => {
render(<Button {...defaultProps} actionType="secondary" />);

const buttonElement = screen.getByRole("button");
expect(buttonElement).toHaveClass("bg-purple-50");
expect(buttonElement).toHaveClass("bg-primary-50");
});

it("renders with actionType as alternative", () => {
Expand Down
10 changes: 5 additions & 5 deletions stories/atoms/buttons/Capsule/Capsule.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -39,12 +39,12 @@ const Capsule = ({
}: ICapsuleProps) => {
const iconStyles = cn(
{ "text-white": actionType === "primary" || actionType === "danger" },
{ "text-purple-700": actionType === "secondary" },
{ "text-primary-700": actionType === "secondary" },
{ "text-gray-700": actionType === "alternative" }
)
const loaderColors = cn(
{ "border-r-white": actionType === "primary" },
{ "border-purple-200 border-r-purple-700": actionType === "secondary" },
{ "border-primary-200 border-r-primary-700": actionType === "secondary" },
{ "border-gray-200 border-r-gray-700": actionType === "alternative" },
{ "border-red-800 border-r-white": actionType === "danger" }
)
Expand All @@ -54,18 +54,18 @@ const Capsule = ({
<button
type="button"
className={cn(
"inline-flex items-center justify-center gap-x-2 text-sm p-2 outline-none focus-visible:ring-1 focus-visible:ring-purple-600 transition-all",
"inline-flex items-center justify-center gap-x-2 text-sm p-2 outline-none focus-visible:ring-1 focus-visible:ring-primary-600 transition-all",
{ "w-full": fullWidth },
{ "px-[11px] py-[7px] text-xs rounded-[15px]": size === "xs" },
{ "px-[15px] py-[9px] text-sm rounded-[17px]": size === "sm" },
{ "px-[17px] py-[9px] text-sm rounded-[19px]": size === "md" },
{ "px-[17px] py-[9px] text-base rounded-[21px]": size === "lg" },
{ "px-[25px] py-[13px] text-base rounded-[25px]": size === "xl" },
{
"bg-purple-600 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-purple-700 hover:bg-purple-700 ": actionType === "primary"
"bg-primary-600 text-white focus-visible:ring-offset-1 focus-visible:ring-offset-white hover:border-primary-700 hover:bg-primary-700 ": actionType === "primary"
},
{
" bg-purple-50 text-purple-700 hover:bg-purple-200 focus-within:bg-purple-100 focus-visible:bg-purple-100 focus:bg-purple-100 active:bg-purple-100":
" bg-primary-50 text-primary-700 hover:bg-primary-200 focus-within:bg-primary-100 focus-visible:bg-primary-100 focus:bg-primary-100 active:bg-primary-100":
actionType === "secondary"
},
{
Expand Down
2 changes: 1 addition & 1 deletion stories/atoms/loaders/Loader.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ const Loader: React.FC<ILoaderProps> = ({ size = "lg", variant = "primary", clas
"rounded-full inline-block animate-spinSlower m-auto",
size === "lg" ? "w-10 h-10 border-2" : size === "md" ? "w-5 h-5 border" : "w-4 h-4 border",
variant === "primary"
? "border-transparent-black-20 border-r-violet-800"
? "border-transparent-black-20 border-r-primary-600"
: variant === "black"
? "border-transparent-black-20 border-r-black"
: "border-transparent-white-50 border-r-white",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ import DropdownWithMultiSelect from "@/stories/organisms/DropdownWithMultiSelect
/**
* Every single-line form control side by side, to check that they share one 36px height and one
* focus language:
* - text inputs, selects and triggers focus with a single 1px violet-700 border (keyboard and mouse);
* - text inputs, selects and triggers focus with a single 1px primary-600 border (keyboard and mouse);
* - buttons, checkboxes, radios and toggles show a 1px ring for keyboard focus only
* (on the edge for outlined controls; with a 1px white offset on filled buttons).
*/
Expand Down Expand Up @@ -396,7 +396,7 @@ export const InputNextToButton: Story = {
};

/**
* Keyboard focus on the control picked in Controls. Text inputs and selects get a 1px violet-700 border;
* Keyboard focus on the control picked in Controls. Text inputs and selects get a 1px primary-600 border;
* buttons, checkboxes, radios and toggles get a 1px ring
* (with a 1px white offset on filled buttons). You can also press Tab in the canvas.
*/
Expand All @@ -407,7 +407,7 @@ export const KeyboardFocus: Story = {
};

/**
* Mouse focus on the control picked in Controls. Text inputs and selects still get the 1px violet-700
* Mouse focus on the control picked in Controls. Text inputs and selects still get the 1px primary-600
* border; buttons, checkboxes, radios and toggles show no ring. You can also click a control in the canvas.
*/
export const MouseFocus: Story = {
Expand Down
2 changes: 1 addition & 1 deletion stories/molecules/inputs/InputField/InputField.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ const InputField = (
// Focus is a single 1px border colour change. The `!` keeps @tailwindcss/forms' focus ring and
// blue border from winning when a consumer's forms styles load after plenum's stylesheet.
"focus:outline-none focus:!ring-0",
{ "focus:!border-violet-700": !isInvalid && !isReadonly },
{ "focus:!border-primary-600": !isInvalid && !isReadonly },
// Disabled: dim the value and placeholder so the field reads as inactive.
"disabled:cursor-not-allowed disabled:text-gray-400 disabled:placeholder:text-gray-300",
isError ? "!border-red-600 !text-red-600" : "",
Expand Down
10 changes: 5 additions & 5 deletions stories/molecules/inputs/InputField/tests/InputField.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,9 +33,9 @@ describe("<InputField>", () => {
expect(getInput()).toHaveClass("focus:!ring-0", "focus:outline-none");
});

it("shows a violet-700 border on focus", () => {
it("shows a primary-600 border on focus", () => {
render(<InputField {...defaultProps} />);
expect(getInput()).toHaveClass("focus:!border-violet-700");
expect(getInput()).toHaveClass("focus:!border-primary-600");
});

it("never adds a focus ring width", () => {
Expand All @@ -46,19 +46,19 @@ describe("<InputField>", () => {
it("keeps the error border on focus", () => {
render(<InputField {...defaultProps} isError />);
expect(getInput()).toHaveClass("!border-red-600");
expect(getInput()).not.toHaveClass("focus:!border-violet-700");
expect(getInput()).not.toHaveClass("focus:!border-primary-600");
expect(getInput()).toHaveAttribute("aria-invalid", "true");
});

it("keeps the error border on focus when a wrapper signals the error with aria-invalid", () => {
render(<InputField {...defaultProps} aria-invalid />);
expect(getInput()).not.toHaveClass("focus:!border-violet-700");
expect(getInput()).not.toHaveClass("focus:!border-primary-600");
});

it("keeps the readonly border on focus", () => {
render(<InputField {...defaultProps} isReadonly />);
expect(getInput()).toHaveClass("!border-gray-400");
expect(getInput()).not.toHaveClass("focus:!border-violet-700");
expect(getInput()).not.toHaveClass("focus:!border-primary-600");
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const NestedInputButton: React.FC<INestedInputButtonProps> = ({
const buttonStyle = cn(
// h-9 matches InputField so the input and add-on form one flush 36px row. Keyboard focus uses the
// same 1px violet border as the input; z-10 lifts it over the input's overlapping edge.
"relative flex h-9 items-center space-x-2 px-4 py-0 leading-5 border border-gray-300 text-sm focus:outline-none focus-visible:z-10 focus-visible:!border-violet-700",
"relative flex h-9 items-center space-x-2 px-4 py-0 leading-5 border border-gray-300 text-sm focus:outline-none focus-visible:z-10 focus-visible:!border-primary-600",
{
"rounded-r text-gray-500 -ml-px": align === "right"
},
Expand Down
2 changes: 1 addition & 1 deletion stories/molecules/inputs/TextInput/TextInput.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -135,7 +135,7 @@ const TextInput = (
"w-full rounded border px-3 text-sm font-normal leading-5 hover:border-gray-500 placeholder:text-gray-500 ",
{ "border-gray-300": !isFocus && !isError && !isDisabled },
{
"!border-violet-700": isFocus && !isError && !isDisabled
"!border-primary-600": isFocus && !isError && !isDisabled
},
{
"!border-red-500": isError
Expand Down
4 changes: 2 additions & 2 deletions stories/molecules/inputs/checkbox/Checkbox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ const Checkbox: FC<ICheckboxProps> = ({
if (!id) id = `cb-${uniqueID}`;

const checkboxStyles = cn(
"rounded-sm border-gray-300 text-sm font-normal leading-5 text-purple-600",
"rounded-sm border-gray-300 text-sm font-normal leading-5 text-primary-600",
// @tailwindcss/forms rings checkboxes on :focus (so also on click); show the ring for keyboard focus only.
"focus:outline-none focus:!ring-0 focus:!ring-offset-0 focus-visible:!ring-1 focus-visible:!ring-purple-600",
"focus:outline-none focus:!ring-0 focus:!ring-offset-0 focus-visible:!ring-1 focus-visible:!ring-primary-600",
{ "border-red-500 shadow-none": isError }
);
const wrapperStyles = cn(
Expand Down
6 changes: 3 additions & 3 deletions stories/molecules/inputs/combobox/ComboBox.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ const Combobox = <T extends Record<string, unknown>>({
"h-9 w-full rounded border py-0 pl-3 pr-10 text-sm leading-5",
// Same focus treatment as InputField: a single 1px violet border, no ring.
"focus:outline-none focus:!ring-0",
isError ? "!border-red-500" : "border-gray-300 focus:!border-violet-700",
isError ? "!border-red-500" : "border-gray-300 focus:!border-primary-600",
"disabled:cursor-not-allowed disabled:text-gray-400 disabled:placeholder:text-gray-300"
)}
onChange={(event) => setQuery(event.target.value)}
Expand Down Expand Up @@ -141,7 +141,7 @@ const Combobox = <T extends Record<string, unknown>>({
className={({ active }) =>
classNames(
"relative cursor-default select-none py-2 pl-3 pr-9",
active ? "bg-purple-600 text-white" : "text-gray-900"
active ? "bg-primary-600 text-white" : "text-gray-900"
)
}
>
Expand All @@ -155,7 +155,7 @@ const Combobox = <T extends Record<string, unknown>>({
<span
className={classNames(
"absolute inset-y-0 right-0 flex items-center pr-4",
active ? "text-white" : "text-purple-600"
active ? "text-white" : "text-primary-600"
)}
>
<DynamicIcon icon="IconCheck" className="h-5 w-5" aria-hidden="true" />
Expand Down
Loading
Loading