@coopdigital/react 0.53.0 → 0.54.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -25,18 +25,18 @@ npm install @coopdigital/react
25
25
 
26
26
  ## Usage
27
27
 
28
- Import the components that you need, along with the main stylesheet and the corresponding component styles:
28
+ Import the components that you need, along with the foundations stylesheet and the corresponding component styles:
29
29
 
30
30
  ```
31
31
  import { Pill } from "@coopdigital/react"
32
- import "@coopdigital/styles/main.css"
32
+ import "@coopdigital/styles/foundations.css"
33
33
  import "@coopdigital/styles/components/Pill.css"
34
34
  ```
35
35
 
36
36
  Alternatively if your project uses SASS you can import the source stylesheets:
37
37
 
38
38
  ```
39
- @use "@coopdigital/styles/src/main.scss"
39
+ @use "@coopdigital/styles/src/foundations.scss"
40
40
  @use "@coopdigital/styles/src/components/Pill.scss"
41
41
  ```
42
42
 
@@ -24,7 +24,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
24
24
  /** **(Optional)** Specify the Button size. */
25
25
  size?: "sm" | "md" | "lg";
26
26
  /** **(Optional)** Specify the Button variant. */
27
- variant?: "green" | "blue" | "white" | "grey" | "green-ghost" | "blue-ghost" | "white-ghost" | "grey-ghost" | "text";
27
+ variant?: "solid" | "ghost" | "text";
28
28
  }
29
29
  export declare const Button: ({ as, children, className, href, isDisabled, isFullWidth, isLoading, loadingText, onClick, ref, size, variant, ...props }: ButtonProps) => JSX.Element;
30
30
  export default Button;
@@ -3,9 +3,10 @@
3
3
  import { jsxs, Fragment, jsx } from 'react/jsx-runtime';
4
4
  import clsx from 'clsx';
5
5
  import React__default, { useState, useCallback } from 'react';
6
+ import { hasUserBg } from '../../utils/index.js';
6
7
  import { LoadingIcon } from '../icons/LoadingIcon.js';
7
8
 
8
- const Button = ({ as, children, className, href, isDisabled = false, isFullWidth = false, isLoading = false, loadingText = "Loading", onClick, ref, size = "md", variant = "green", ...props }) => {
9
+ const Button = ({ as, children, className, href, isDisabled = false, isFullWidth = false, isLoading = false, loadingText = "Loading", onClick, ref, size = "md", variant = "solid", ...props }) => {
9
10
  const element = as !== null && as !== void 0 ? as : (href ? "a" : "button");
10
11
  const [isPending, setIsPending] = useState(false);
11
12
  const handleClick = useCallback(async (event) => {
@@ -22,7 +23,7 @@ const Button = ({ as, children, className, href, isDisabled = false, isFullWidth
22
23
  const componentProps = {
23
24
  "aria-disabled": isDisabled ? true : undefined,
24
25
  "aria-live": "assertive",
25
- className: clsx(variant == "text" ? "coop-link" : "coop-button", className),
26
+ className: clsx(variant == "text" ? "coop-link" : "coop-button", !hasUserBg(className) && variant === "solid" && "bg-teal", className),
26
27
  "data-loading": isLoading || isPending ? true : undefined,
27
28
  "data-size": size.length && size !== "md" ? size : undefined,
28
29
  "data-variant": variant !== "text" ? variant : undefined,
@@ -2,6 +2,7 @@ import { jsxs, jsx, Fragment } from 'react/jsx-runtime';
2
2
  import clsx from 'clsx';
3
3
  import React__default from 'react';
4
4
  import { useSlots } from '../../hooks/useSlots.js';
5
+ import { hasUserBg } from '../../utils/index.js';
5
6
  import { ChevronRightIcon } from '../icons/ChevronRightIcon.js';
6
7
  import { Image } from '../Image/Image.js';
7
8
 
@@ -28,7 +29,7 @@ const Card = ({ chevron = false, children, className, href, hrefAs, imagePositio
28
29
  const innerProps = { className: "coop-card--inner" };
29
30
  const hasLinkWrapper = href && !slots.CardHeading;
30
31
  const componentProps = {
31
- className: clsx("coop-card", className),
32
+ className: clsx("coop-card", !hasUserBg(className) && "bg-white", className),
32
33
  "data-image-pos": imagePosition,
33
34
  "data-orientation": orientation !== "vertical" ? orientation : undefined,
34
35
  ...props,
@@ -130,7 +130,7 @@ const DatePicker = ({ className, closeOnSelect = true, dateFormat = "dd/MM/yyyy"
130
130
  // eslint-disable-next-line jsx-a11y/no-autofocus
131
131
  autoFocus: true, captionLayout: "dropdown",
132
132
  //defaultMonth={defaultMonth}
133
- disabled: disabledDates, endMonth: endDate, month: state.view, onMonthChange: setView, onSelect: updateValues, startMonth: startDate, ...calendarProps[mode] }), jsxs("div", { className: "coop-datepicker-actions", children: [jsxs(Button, { "aria-label": "Cancel", onClick: resetState, size: "sm", variant: "grey", children: ["Clear ", jsx(CloseIcon, { stroke: "black", strokeWidth: 1 })] }), jsx(Popover.Close, { asChild: true, children: jsxs(Button, { "aria-label": "Accept", size: "sm", children: ["OK ", jsx(TickIcon, { stroke: "white", strokeWidth: 1 })] }) })] })] })] }), jsx("div", { "aria-live": "assertive", className: "sr-only coop-datepicker-status", role: "status", children: (_g = state.message) !== null && _g !== void 0 ? _g : footerMessage[mode] }), mode === "single" && jsx("input", { ...valueProps, value: (_j = (_h = state.single) === null || _h === void 0 ? void 0 : _h.field) !== null && _j !== void 0 ? _j : "" }), mode === "multiple" && jsx("input", { ...valueProps, value: (_l = (_k = state.multiple) === null || _k === void 0 ? void 0 : _k.field) !== null && _l !== void 0 ? _l : "" }), mode === "range" && (jsxs(Fragment, { children: [jsx("input", { ...baseValueProps, id: `${uid}_start`, name: `${name}_start`, value: (_p = (_o = (_m = state.range) === null || _m === void 0 ? void 0 : _m.field) === null || _o === void 0 ? void 0 : _o.from) !== null && _p !== void 0 ? _p : "" }), jsx("input", { ...baseValueProps, id: `${uid}_end`, name: `${name}_end`, value: (_s = (_r = (_q = state.range) === null || _q === void 0 ? void 0 : _q.field) === null || _r === void 0 ? void 0 : _r.to) !== null && _s !== void 0 ? _s : "" })] }))] }));
133
+ disabled: disabledDates, endMonth: endDate, month: state.view, onMonthChange: setView, onSelect: updateValues, startMonth: startDate, ...calendarProps[mode] }), jsxs("div", { className: "coop-datepicker-actions", children: [jsxs(Button, { "aria-label": "Cancel", className: "bg-tint-grey", onClick: resetState, size: "sm", children: ["Clear ", jsx(CloseIcon, { stroke: "black", strokeWidth: 1 })] }), jsx(Popover.Close, { asChild: true, children: jsxs(Button, { "aria-label": "Accept", size: "sm", children: ["OK ", jsx(TickIcon, { stroke: "white", strokeWidth: 1 })] }) })] })] })] }), jsx("div", { "aria-live": "assertive", className: "sr-only coop-datepicker-status", role: "status", children: (_g = state.message) !== null && _g !== void 0 ? _g : footerMessage[mode] }), mode === "single" && jsx("input", { ...valueProps, value: (_j = (_h = state.single) === null || _h === void 0 ? void 0 : _h.field) !== null && _j !== void 0 ? _j : "" }), mode === "multiple" && jsx("input", { ...valueProps, value: (_l = (_k = state.multiple) === null || _k === void 0 ? void 0 : _k.field) !== null && _l !== void 0 ? _l : "" }), mode === "range" && (jsxs(Fragment, { children: [jsx("input", { ...baseValueProps, id: `${uid}_start`, name: `${name}_start`, value: (_p = (_o = (_m = state.range) === null || _m === void 0 ? void 0 : _m.field) === null || _o === void 0 ? void 0 : _o.from) !== null && _p !== void 0 ? _p : "" }), jsx("input", { ...baseValueProps, id: `${uid}_end`, name: `${name}_end`, value: (_s = (_r = (_q = state.range) === null || _q === void 0 ? void 0 : _q.field) === null || _r === void 0 ? void 0 : _r.to) !== null && _s !== void 0 ? _s : "" })] }))] }));
134
134
  };
135
135
  DatePicker.config = componentConfig;
136
136
 
@@ -1,7 +1,7 @@
1
1
  import { jsxs, jsx } from 'react/jsx-runtime';
2
2
  import { clsx } from 'clsx';
3
3
  import { useSlots } from '../../hooks/useSlots.js';
4
- import { hasUserBg, bgClassToColor } from '../../utils/index.js';
4
+ import { hasUserBg } from '../../utils/index.js';
5
5
  import 'react';
6
6
  import { ChevronDownIcon } from '../icons/ChevronDownIcon.js';
7
7
 
@@ -15,9 +15,9 @@ const Expandable = ({ children, className, ref, ...props }) => {
15
15
  className: clsx("coop-expandable", !hasUserBg(className) && "bg-tint-grey", className),
16
16
  ...props,
17
17
  };
18
- componentProps.style = {
19
- "--bg": `var(--color-${bgClassToColor(componentProps.className)})`,
20
- };
18
+ // componentProps.style = {
19
+ // "--bg": `var(--color-${bgClassToColor(componentProps.className)})`,
20
+ // }
21
21
  return (jsxs("details", { ...componentProps, ref: ref, children: [slots.ExpandableSummary, slots.ExpandableContent] }));
22
22
  };
23
23
  const ExpandableSummary = ({ children, className, ...props }) => {
@@ -22,7 +22,7 @@ const Pill = ({ as, children, className, href, ref, size = "md", ...props }) =>
22
22
  return React__default.createElement(element, { ...componentProps, ref }, slots.PillBadge, slots.Children);
23
23
  };
24
24
  const PillBadge = ({ children, className }) => {
25
- return (jsx("span", { className: clsx("coop-pill--badge", !hasUserBg(className) && "bg-offer-red", className), children: children }));
25
+ return (jsx("span", { className: clsx("coop-pill--badge", !hasUserBg(className) && "bg-red", className), children: children }));
26
26
  };
27
27
  PillBadge.config = { name: "PillBadge" };
28
28
  Pill.Badge = PillBadge;
@@ -1,8 +1,9 @@
1
- import type { InputHTMLAttributes, JSX, Ref } from "react";
1
+ import type { FormHTMLAttributes, JSX, Ref } from "react";
2
2
  import React from "react";
3
3
  import { StandardSizes } from "../../types";
4
4
  import { type ButtonProps } from "../Button";
5
- export interface SearchboxProps extends Omit<InputHTMLAttributes<HTMLInputElement>, "size" | "type"> {
5
+ import { TextInputProps } from "../TextInput";
6
+ export interface SearchboxProps extends FormHTMLAttributes<HTMLFormElement> {
6
7
  /** **(Optional)** Specify a server endpoint to submit the form. Will be ignored if onSubmit is also set. */
7
8
  action?: string;
8
9
  /** **(Optional)** Specify props to forward to the Button element. Use `label` to set Button text. */
@@ -28,7 +29,22 @@ export interface SearchboxProps extends Omit<InputHTMLAttributes<HTMLInputElemen
28
29
  /** **(Optional)** Specify the Searchbox size. */
29
30
  size?: StandardSizes;
30
31
  /** **(Optional)** Specify the Searchbox variant. */
31
- variant?: "green" | "blue" | "white" | "grey" | "green-ghost" | "blue-ghost" | "white-ghost" | "grey-ghost";
32
+ variant?: "solid" | "ghost";
32
33
  }
33
- export declare const Searchbox: ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize, autoComplete, button, className, id, label, labelVisible, name, onSubmit, placeholder, ref, size, variant, ...props }: SearchboxProps) => JSX.Element;
34
+ export declare const Searchbox: {
35
+ ({ action, children, className, id, label, labelVisible, onSubmit, ref, size, variant, ...props }: SearchboxProps): JSX.Element;
36
+ Button: {
37
+ ({ children, ...props }: ButtonProps): import("react/jsx-runtime").JSX.Element;
38
+ config: {
39
+ name: string;
40
+ };
41
+ };
42
+ Input: {
43
+ (props: TextInputProps): import("react/jsx-runtime").JSX.Element;
44
+ config: {
45
+ isField: boolean;
46
+ name: string;
47
+ };
48
+ };
49
+ };
34
50
  export default Searchbox;
@@ -1,21 +1,27 @@
1
1
 
2
2
  "use client";
3
- import { jsxs, jsx } from 'react/jsx-runtime';
3
+ import { jsx, jsxs } from 'react/jsx-runtime';
4
4
  import clsx from 'clsx';
5
5
  import React__default, { useState, useCallback } from 'react';
6
6
  import { useId } from '../../hooks/useId.js';
7
+ import { useSlots } from '../../hooks/useSlots.js';
8
+ import { hasUserBorder } from '../../utils/index.js';
7
9
  import { Button } from '../Button/Button.js';
8
10
  import { Field } from '../Field/Field.js';
9
- import { Label } from '../FieldMarkers/Label.js';
10
11
  import { SearchIcon } from '../icons/SearchIcon.js';
11
12
  import { TextInput } from '../TextInput/TextInput.js';
12
13
 
13
- const defaultButtonProps = {
14
- label: React__default.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
15
- loadingText: "",
14
+ const componentSlots = {
15
+ SearchboxButton: null,
16
+ SearchboxInput: null,
16
17
  };
17
- const Searchbox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize = "off", autoComplete = "off", button = defaultButtonProps, className, id, label, labelVisible = false, name = "query", onSubmit, placeholder, ref, size = "md", variant = "green", ...props }) => {
18
- var _a, _b;
18
+ const defaultButtonText = React__default.createElement(SearchIcon, {
19
+ alt: "Search",
20
+ stroke: "currentColor",
21
+ strokeWidth: 2,
22
+ });
23
+ const Searchbox = ({ action, children, className, id, label, labelVisible = false, onSubmit, ref, size = "md", variant = "solid", ...props }) => {
24
+ const slots = useSlots(componentSlots, children);
19
25
  const [isPending, setIsPending] = useState(false);
20
26
  const uid = useId(id);
21
27
  const handleSubmit = useCallback(async (event) => {
@@ -30,37 +36,45 @@ const Searchbox = ({ action, "aria-placeholder": ariaPlaceholder, autoCapitalize
30
36
  setIsPending(false);
31
37
  }
32
38
  }, [onSubmit, isPending]);
39
+ let borderClass = "";
40
+ if (!hasUserBorder(className)) {
41
+ borderClass = variant === "ghost" ? "border-teal" : "border-grey";
42
+ }
33
43
  const formProps = {
34
44
  action: action !== null && action !== void 0 ? action : undefined,
35
- className: clsx("coop-searchbox", className),
45
+ className: clsx("coop-searchbox", borderClass, className),
36
46
  "data-size": size && size !== "md" ? size : undefined,
37
- "data-variant": variant.length && variant !== "green" ? variant : undefined,
38
- id: uid,
47
+ "data-variant": variant.length && variant !== "solid" ? variant : undefined,
48
+ id: uid + "-form",
39
49
  onSubmit: onSubmit ? handleSubmit : undefined,
50
+ ...props,
40
51
  };
41
- const buttonProps = {
42
- className: button === null || button === void 0 ? void 0 : button.className,
52
+ slots.SearchboxButton = React__default.cloneElement(slots.SearchboxButton, {
43
53
  isLoading: isPending,
44
- loadingText: (_a = button === null || button === void 0 ? void 0 : button.loadingText) !== null && _a !== void 0 ? _a : "",
45
54
  size,
46
- variant,
47
- };
48
- const inputProps = {
49
- "aria-placeholder": (_b = placeholder !== null && placeholder !== void 0 ? placeholder : ariaPlaceholder) !== null && _b !== void 0 ? _b : undefined,
50
- autoCapitalize,
51
- autoComplete,
52
- id: uid + "--input",
53
- name,
54
- placeholder,
55
+ });
56
+ slots.SearchboxInput = React__default.cloneElement(slots.SearchboxInput, {
57
+ autoCapitalize: "off",
58
+ id: uid,
55
59
  size,
56
- type: "search",
57
- ...props,
58
- };
60
+ });
59
61
  const labelProps = {
60
- htmlFor: uid + "--input",
62
+ htmlFor: uid,
61
63
  isVisible: labelVisible,
62
64
  };
63
- return (jsxs("form", { ...formProps, ref: ref, children: [label && jsx(Label, { ...labelProps, children: label }), jsxs("div", { className: "coop-searchbox--inner", children: [jsx(Field, { children: jsx(TextInput, { ...inputProps }) }), jsx(Button, { ...buttonProps, children: button.label })] })] }));
65
+ return (jsxs("form", { ...formProps, ref: ref, children: [label && jsx(Field.Label, { ...labelProps, children: label }), jsxs("div", { className: clsx("coop-searchbox--inner"), children: [jsx(Field, { children: slots.SearchboxInput }), slots.SearchboxButton] })] }));
66
+ };
67
+ const SearchboxButton = ({ children, ...props }) => {
68
+ const buttonProps = {
69
+ children: children !== null && children !== void 0 ? children : defaultButtonText,
70
+ ...props,
71
+ };
72
+ return jsx(Button, { ...buttonProps });
64
73
  };
74
+ const SearchboxInput = (props) => jsx(TextInput, { ...props });
75
+ Searchbox.Button = SearchboxButton;
76
+ Searchbox.Input = SearchboxInput;
77
+ SearchboxButton.config = { name: "SearchboxButton" };
78
+ SearchboxInput.config = { isField: true, name: "SearchboxInput" };
65
79
 
66
80
  export { Searchbox, Searchbox as default };
@@ -4,7 +4,7 @@ import { hasUserBg } from '../../utils/index.js';
4
4
 
5
5
  const Squircle = ({ children, className, ref, size = "lg", ...props }) => {
6
6
  const componentProps = {
7
- className: clsx("coop-squircle", !hasUserBg(className) && "bg-offer-red", className),
7
+ className: clsx("coop-squircle", !hasUserBg(className) && "bg-offer", className),
8
8
  "data-size": size.length && size !== "lg" ? size : undefined,
9
9
  ...props,
10
10
  };
@@ -1,11 +1,10 @@
1
1
  export type Darks = "dark-blue" | "dark-yellow" | "dark-green" | "dark-lilac" | "dark-orange" | "dark-pink" | "dark-purple" | "dark-red";
2
2
  export type Tints = "tint-blue" | "tint-brown" | "tint-yellow" | "tint-green" | "tint-grey" | "tint-lilac" | "tint-orange" | "tint-pink" | "tint-purple" | "tint-red";
3
3
  export type Lights = "light-blue" | "light-yellow" | "light-green" | "light-lilac" | "light-orange" | "light-pink" | "light-purple" | "light-red";
4
- export type Greys = "dark-grey" | "mid-dark-grey" | "mid-grey" | "mid-light-grey" | "light-grey";
5
4
  export type White = "white";
6
5
  export type Black = "black";
7
6
  export type None = "none";
8
7
  export type BrandBlue = "brand-blue";
9
- export type OfferRed = "offer-red";
8
+ export type OfferRed = "offer";
10
9
  export type Green = "green";
11
10
  export type Blue = "blue";
@@ -1,3 +1,4 @@
1
1
  export declare const bgPropToClass: (color: string, userClasses?: string) => string;
2
2
  export declare const hasUserBg: (userClasses?: string) => boolean;
3
+ export declare const hasUserBorder: (userClasses?: string) => boolean;
3
4
  export declare const bgClassToColor: (userClasses: string) => string | null;
@@ -1,6 +1,9 @@
1
1
  const hasUserBg = (userClasses) => {
2
2
  return typeof userClasses === "string" && (userClasses === null || userClasses === void 0 ? void 0 : userClasses.includes("bg-")) ? true : false;
3
3
  };
4
+ const hasUserBorder = (userClasses) => {
5
+ return typeof userClasses === "string" && (userClasses === null || userClasses === void 0 ? void 0 : userClasses.includes("border-")) ? true : false;
6
+ };
4
7
  const bgClassToColor = (userClasses) => {
5
8
  var _a, _b;
6
9
  return (_b = (_a = /bg-([^\s]+)/.exec(userClasses)) === null || _a === void 0 ? void 0 : _a[1]) !== null && _b !== void 0 ? _b : null;
@@ -12,4 +15,4 @@ const bgClassToColor = (userClasses) => {
12
15
  // )
13
16
  };
14
17
 
15
- export { bgClassToColor, hasUserBg };
18
+ export { bgClassToColor, hasUserBg, hasUserBorder };
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@coopdigital/react",
3
3
  "type": "module",
4
- "version": "0.53.0",
4
+ "version": "0.54.0",
5
5
  "private": false,
6
6
  "publishConfig": {
7
7
  "access": "public"
@@ -58,22 +58,22 @@
58
58
  "license": "MIT",
59
59
  "description": "React components for the Experience Library design system",
60
60
  "devDependencies": {
61
- "@axe-core/playwright": "^4.11.0",
61
+ "@axe-core/playwright": "^4.11.1",
62
62
  "@playwright/test": "^1.58.1",
63
- "@storybook/addon-a11y": "^10.2.6",
64
- "@storybook/addon-docs": "^10.2.6",
65
- "@storybook/addon-onboarding": "^10.2.6",
66
- "@storybook/react-vite": "^10.2.6",
63
+ "@storybook/addon-a11y": "^10.2.7",
64
+ "@storybook/addon-docs": "^10.2.7",
65
+ "@storybook/addon-onboarding": "^10.2.7",
66
+ "@storybook/react-vite": "^10.2.7",
67
67
  "@testing-library/jest-dom": "^6.9.1",
68
68
  "@testing-library/react": "^16.3.2",
69
- "@types/react": "^19.2.10",
69
+ "@types/react": "^19.2.13",
70
70
  "@types/react-dom": "^19.2.3",
71
71
  "react": "^19.2.4",
72
72
  "react-dom": "^19.2.4",
73
73
  "resize-observer-polyfill": "^1.5.1",
74
74
  "serve": "^14.2.5",
75
- "storybook": "^10.2.6",
76
- "storybook-addon-tag-badges": "^3.0.5"
75
+ "storybook": "^10.2.7",
76
+ "storybook-addon-tag-badges": "^3.0.6"
77
77
  },
78
78
  "peerDependencies": {
79
79
  "react": "^19.1.0",
@@ -83,10 +83,10 @@
83
83
  "storybook": "$storybook"
84
84
  },
85
85
  "dependencies": {
86
- "@coopdigital/styles": "^0.44.0",
86
+ "@coopdigital/styles": "^0.45.0",
87
87
  "@radix-ui/react-popover": "^1.1.15",
88
88
  "clsx": "^2.1.1",
89
89
  "react-day-picker": "^9.12.0"
90
90
  },
91
- "gitHead": "d8324f6ee7c544c37f752b5e6eeb67cffff20237"
91
+ "gitHead": "d15d7ae0d231bf28986a18a9e097537802f845db"
92
92
  }
@@ -5,6 +5,7 @@ import type { ButtonHTMLAttributes, ForwardRefExoticComponent, JSX, Ref } from "
5
5
  import clsx from "clsx"
6
6
  import React, { useCallback, useState } from "react"
7
7
 
8
+ import { hasUserBg } from "../../utils"
8
9
  import { LoadingIcon } from "../icons"
9
10
 
10
11
  export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
@@ -32,16 +33,7 @@ export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
32
33
  /** **(Optional)** Specify the Button size. */
33
34
  size?: "sm" | "md" | "lg"
34
35
  /** **(Optional)** Specify the Button variant. */
35
- variant?:
36
- | "green"
37
- | "blue"
38
- | "white"
39
- | "grey"
40
- | "green-ghost"
41
- | "blue-ghost"
42
- | "white-ghost"
43
- | "grey-ghost"
44
- | "text"
36
+ variant?: "solid" | "ghost" | "text"
45
37
  }
46
38
 
47
39
  type OnClickHandler =
@@ -60,7 +52,7 @@ export const Button = ({
60
52
  onClick,
61
53
  ref,
62
54
  size = "md",
63
- variant = "green",
55
+ variant = "solid",
64
56
  ...props
65
57
  }: ButtonProps): JSX.Element => {
66
58
  const element: ButtonProps["as"] = as ?? (href ? "a" : "button")
@@ -85,7 +77,11 @@ export const Button = ({
85
77
  const componentProps = {
86
78
  "aria-disabled": isDisabled ? true : undefined,
87
79
  "aria-live": "assertive" as keyof ButtonHTMLAttributes<HTMLButtonElement>["aria-live"],
88
- className: clsx(variant == "text" ? "coop-link" : "coop-button", className),
80
+ className: clsx(
81
+ variant == "text" ? "coop-link" : "coop-button",
82
+ !hasUserBg(className) && variant === "solid" && "bg-teal",
83
+ className
84
+ ),
89
85
  "data-loading": isLoading || isPending ? true : undefined,
90
86
  "data-size": size.length && size !== "md" ? size : undefined,
91
87
  "data-variant": variant !== "text" ? variant : undefined,
@@ -4,6 +4,7 @@ import clsx from "clsx"
4
4
  import React from "react"
5
5
 
6
6
  import { useSlots } from "../../hooks/useSlots"
7
+ import { hasUserBg } from "../../utils"
7
8
  import { ChevronRightIcon } from "../icons/ChevronRightIcon"
8
9
  import { Image, ImageProps } from "../Image"
9
10
 
@@ -99,7 +100,7 @@ export const Card = ({
99
100
  const hasLinkWrapper = href && !slots.CardHeading
100
101
 
101
102
  const componentProps = {
102
- className: clsx("coop-card", className),
103
+ className: clsx("coop-card", !hasUserBg(className) && "bg-white", className),
103
104
  "data-image-pos": imagePosition,
104
105
  "data-orientation": orientation !== "vertical" ? orientation : undefined,
105
106
  ...props,
@@ -330,7 +330,7 @@ export const DatePicker = ({
330
330
  {...calendarProps[mode]}
331
331
  />
332
332
  <div className="coop-datepicker-actions">
333
- <Button aria-label="Cancel" onClick={resetState} size="sm" variant="grey">
333
+ <Button aria-label="Cancel" className="bg-tint-grey" onClick={resetState} size="sm">
334
334
  Clear <CloseIcon stroke="black" strokeWidth={1} />
335
335
  </Button>
336
336
  <Popover.Close asChild>
@@ -3,7 +3,7 @@ import type { DetailsHTMLAttributes, HTMLAttributes, JSX, Ref } from "react"
3
3
  import { clsx } from "clsx"
4
4
 
5
5
  import { useSlots } from "../../hooks/useSlots"
6
- import { bgClassToColor, hasUserBg } from "../../utils"
6
+ import { hasUserBg } from "../../utils"
7
7
  import { ChevronDownIcon } from "../icons"
8
8
 
9
9
  export interface ExpandableProps extends DetailsHTMLAttributes<HTMLDetailsElement> {
@@ -47,9 +47,9 @@ export const Expandable = ({
47
47
  ...props,
48
48
  }
49
49
 
50
- componentProps.style = {
51
- "--bg": `var(--color-${bgClassToColor(componentProps.className)})`,
52
- }
50
+ // componentProps.style = {
51
+ // "--bg": `var(--color-${bgClassToColor(componentProps.className)})`,
52
+ // }
53
53
 
54
54
  return (
55
55
  <details {...componentProps} ref={ref}>
@@ -62,7 +62,7 @@ export const Pill = ({
62
62
 
63
63
  const PillBadge = ({ children, className }: PillBadgeProps) => {
64
64
  return (
65
- <span className={clsx("coop-pill--badge", !hasUserBg(className) && "bg-offer-red", className)}>
65
+ <span className={clsx("coop-pill--badge", !hasUserBg(className) && "bg-red", className)}>
66
66
  {children}
67
67
  </span>
68
68
  )
@@ -1,22 +1,20 @@
1
1
  "use client"
2
2
 
3
- import type { InputHTMLAttributes, JSX, Ref } from "react"
3
+ import type { FormHTMLAttributes, JSX, Ref } from "react"
4
4
 
5
5
  import clsx from "clsx"
6
6
  import React, { useCallback, useState } from "react"
7
7
 
8
8
  import { useId } from "../../hooks/useId"
9
+ import { useSlots } from "../../hooks/useSlots"
9
10
  import { StandardSizes } from "../../types"
11
+ import { hasUserBorder } from "../../utils"
10
12
  import { Button, type ButtonProps } from "../Button"
11
13
  import Field from "../Field"
12
- import { Label as FieldLabel } from "../FieldMarkers/Label"
13
14
  import { SearchIcon } from "../icons"
14
15
  import TextInput, { TextInputProps } from "../TextInput"
15
16
 
16
- export interface SearchboxProps extends Omit<
17
- InputHTMLAttributes<HTMLInputElement>,
18
- "size" | "type"
19
- > {
17
+ export interface SearchboxProps extends FormHTMLAttributes<HTMLFormElement> {
20
18
  /** **(Optional)** Specify a server endpoint to submit the form. Will be ignored if onSubmit is also set. */
21
19
  action?: string
22
20
  /** **(Optional)** Specify props to forward to the Button element. Use `label` to set Button text. */
@@ -40,46 +38,40 @@ export interface SearchboxProps extends Omit<
40
38
  /** **(Optional)** Specify the Searchbox size. */
41
39
  size?: StandardSizes
42
40
  /** **(Optional)** Specify the Searchbox variant. */
43
- variant?:
44
- | "green"
45
- | "blue"
46
- | "white"
47
- | "grey"
48
- | "green-ghost"
49
- | "blue-ghost"
50
- | "white-ghost"
51
- | "grey-ghost"
41
+ variant?: "solid" | "ghost"
52
42
  }
53
43
 
54
- const defaultButtonProps: SearchboxProps["button"] = {
55
- label: React.createElement(SearchIcon, { alt: "Search", stroke: "currentColor", strokeWidth: 2 }),
56
- loadingText: "",
44
+ const componentSlots = {
45
+ SearchboxButton: null,
46
+ SearchboxInput: null,
57
47
  }
58
48
 
49
+ const defaultButtonText = React.createElement(SearchIcon, {
50
+ alt: "Search",
51
+ stroke: "currentColor",
52
+ strokeWidth: 2,
53
+ })
54
+
59
55
  export const Searchbox = ({
60
56
  action,
61
- "aria-placeholder": ariaPlaceholder,
62
- autoCapitalize = "off",
63
- autoComplete = "off",
64
- button = defaultButtonProps,
57
+ children,
65
58
  className,
66
-
67
59
  id,
68
60
  label,
69
61
  labelVisible = false,
70
- name = "query",
71
62
  onSubmit,
72
- placeholder,
73
63
  ref,
74
64
  size = "md",
75
- variant = "green",
65
+ variant = "solid",
76
66
  ...props
77
67
  }: SearchboxProps): JSX.Element => {
68
+ const slots = useSlots(componentSlots, children)
69
+
78
70
  const [isPending, setIsPending] = useState(false)
79
71
  const uid = useId(id)
80
72
 
81
73
  const handleSubmit = useCallback(
82
- async (event: React.FormEvent<HTMLFormElement>) => {
74
+ async (event: React.SubmitEvent<HTMLFormElement>) => {
83
75
  event.preventDefault()
84
76
 
85
77
  if (isPending || !onSubmit) return
@@ -95,51 +87,69 @@ export const Searchbox = ({
95
87
  [onSubmit, isPending]
96
88
  )
97
89
 
90
+ let borderClass = ""
91
+ if (!hasUserBorder(className)) {
92
+ borderClass = variant === "ghost" ? "border-teal" : "border-grey"
93
+ }
94
+
98
95
  const formProps = {
99
96
  action: action ?? undefined,
100
- className: clsx("coop-searchbox", className),
97
+ className: clsx("coop-searchbox", borderClass, className),
101
98
  "data-size": size && size !== "md" ? size : undefined,
102
- "data-variant": variant.length && variant !== "green" ? variant : undefined,
103
- id: uid,
99
+ "data-variant": variant.length && variant !== "solid" ? variant : undefined,
100
+ id: uid + "-form",
104
101
  onSubmit: onSubmit ? handleSubmit : undefined,
102
+ ...props,
105
103
  }
106
104
 
107
- const buttonProps: ButtonProps = {
108
- className: button?.className,
109
- isLoading: isPending,
110
- loadingText: button?.loadingText ?? "",
111
- size,
112
- variant,
113
- }
105
+ slots.SearchboxButton = React.cloneElement(
106
+ slots.SearchboxButton as React.ReactElement<ButtonProps>,
107
+ {
108
+ isLoading: isPending,
109
+ size,
110
+ }
111
+ )
114
112
 
115
- const inputProps: TextInputProps = {
116
- "aria-placeholder": placeholder ?? ariaPlaceholder ?? undefined,
117
- autoCapitalize,
118
- autoComplete,
119
- id: uid + "--input",
120
- name,
121
- placeholder,
122
- size,
123
- type: "search",
124
- ...props,
125
- }
113
+ slots.SearchboxInput = React.cloneElement(
114
+ slots.SearchboxInput as React.ReactElement<TextInputProps>,
115
+ {
116
+ autoCapitalize: "off",
117
+ id: uid,
118
+ size,
119
+ }
120
+ )
126
121
 
127
122
  const labelProps = {
128
- htmlFor: uid + "--input",
123
+ htmlFor: uid,
129
124
  isVisible: labelVisible,
130
125
  }
131
126
 
132
127
  return (
133
128
  <form {...formProps} ref={ref}>
134
- {label && <FieldLabel {...labelProps}>{label}</FieldLabel>}
135
- <div className="coop-searchbox--inner">
136
- <Field>
137
- <TextInput {...inputProps} />
138
- </Field>
139
- <Button {...buttonProps}>{button.label}</Button>
129
+ {label && <Field.Label {...labelProps}>{label}</Field.Label>}
130
+ <div className={clsx("coop-searchbox--inner")}>
131
+ <Field>{slots.SearchboxInput}</Field>
132
+ {slots.SearchboxButton}
140
133
  </div>
141
134
  </form>
142
135
  )
143
136
  }
144
137
 
138
+ const SearchboxButton = ({ children, ...props }: ButtonProps) => {
139
+ const buttonProps = {
140
+ children: children ?? defaultButtonText,
141
+ ...props,
142
+ }
143
+
144
+ return <Button {...buttonProps} />
145
+ }
146
+
147
+ const SearchboxInput = (props: TextInputProps) => <TextInput {...props} />
148
+
149
+ Searchbox.Button = SearchboxButton
150
+ Searchbox.Input = SearchboxInput
151
+
152
+ SearchboxButton.config = { name: "SearchboxButton" }
153
+ SearchboxInput.config = { isField: true, name: "SearchboxInput" }
154
+
145
155
  export default Searchbox
@@ -22,7 +22,7 @@ export const Squircle = ({
22
22
  ...props
23
23
  }: SquircleProps): JSX.Element => {
24
24
  const componentProps = {
25
- className: clsx("coop-squircle", !hasUserBg(className) && "bg-offer-red", className),
25
+ className: clsx("coop-squircle", !hasUserBg(className) && "bg-offer", className),
26
26
  "data-size": size.length && size !== "lg" ? size : undefined,
27
27
  ...props,
28
28
  }
@@ -30,12 +30,10 @@ export type Lights =
30
30
  | "light-purple"
31
31
  | "light-red"
32
32
 
33
- export type Greys = "dark-grey" | "mid-dark-grey" | "mid-grey" | "mid-light-grey" | "light-grey"
34
-
35
33
  export type White = "white"
36
34
  export type Black = "black"
37
35
  export type None = "none"
38
36
  export type BrandBlue = "brand-blue"
39
- export type OfferRed = "offer-red"
37
+ export type OfferRed = "offer"
40
38
  export type Green = "green"
41
39
  export type Blue = "blue"
@@ -5,6 +5,10 @@ export const hasUserBg = (userClasses?: string) => {
5
5
  return typeof userClasses === "string" && userClasses?.includes("bg-") ? true : false
6
6
  }
7
7
 
8
+ export const hasUserBorder = (userClasses?: string) => {
9
+ return typeof userClasses === "string" && userClasses?.includes("border-") ? true : false
10
+ }
11
+
8
12
  export const bgClassToColor = (userClasses: string) => {
9
13
  return /bg-([^\s]+)/.exec(userClasses)?.[1] ?? null
10
14