@agility/plenum-ui 2.0.0-rc2 → 2.0.0-rc4

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.
Files changed (25) hide show
  1. package/dist/index.d.ts +15 -11
  2. package/dist/index.js +111 -66
  3. package/dist/index.js.map +2 -2
  4. package/dist/types/stories/atoms/buttons/Button/Alternative/Alternative.stories.d.ts +1 -0
  5. package/dist/types/stories/atoms/buttons/Button/Button.d.ts +6 -4
  6. package/dist/types/stories/atoms/buttons/Button/Danger/Danger.stories.d.ts +1 -0
  7. package/dist/types/stories/atoms/buttons/Button/Primary/Primary.stories.d.ts +1 -0
  8. package/dist/types/stories/atoms/buttons/Button/Secondary/Secondary.stories.d.ts +1 -0
  9. package/dist/types/stories/molecules/inputs/InputField/InputField.d.ts +2 -0
  10. package/dist/types/stories/organisms/DropdownComponent/DropdownComponent.d.ts +3 -7
  11. package/package.json +2 -2
  12. package/stories/atoms/buttons/Button/Alternative/Alternative.stories.ts +7 -0
  13. package/stories/atoms/buttons/Button/Button.tsx +25 -2
  14. package/stories/atoms/buttons/Button/Danger/Danger.stories.ts +7 -0
  15. package/stories/atoms/buttons/Button/Primary/Primary.stories.ts +7 -0
  16. package/stories/atoms/buttons/Button/Secondary/Secondary.stories.ts +7 -0
  17. package/stories/molecules/inputs/InputCounter/InputCounter.stories.tsx +18 -0
  18. package/stories/molecules/inputs/InputCounter/InputCounter.tsx +24 -0
  19. package/stories/molecules/inputs/InputCounter/index.tsx +3 -0
  20. package/stories/molecules/inputs/InputField/InputField.tsx +6 -1
  21. package/stories/molecules/inputs/TextInput/TextInput.stories.tsx +32 -0
  22. package/stories/molecules/inputs/TextInput/TextInput.tsx +162 -0
  23. package/stories/molecules/inputs/TextInput/index.tsx +3 -0
  24. package/stories/organisms/DropdownComponent/DropdownComponent.tsx +111 -80
  25. package/stories/organisms/DropdownComponent/dropdownItems.ts +8 -8
@@ -7,6 +7,7 @@ export declare const Alternative: Story;
7
7
  export declare const AlternativeTrailingIcon: Story;
8
8
  export declare const AlternativeLeadingIcon: Story;
9
9
  export declare const AlternativeExtraSmall: Story;
10
+ export declare const AlternativeSimpleIconName: Story;
10
11
  export declare const AlternativeSmall: Story;
11
12
  export declare const AlternativeMedium: Story;
12
13
  export declare const AlternativeLarge: Story;
@@ -1,15 +1,15 @@
1
1
  import { HTMLAttributeAnchorTarget } from "react";
2
- import { IDynamicIconProps } from "../../icons";
2
+ import { UnifiedIconName, IDynamicIconProps } from "../../icons";
3
3
  export type BTNActionType = "primary" | "secondary" | "alternative" | "danger";
4
4
  export interface IButtonProps extends React.ComponentPropsWithoutRef<"button"> {
5
5
  /** Is the button a Primary CTA, alternative or danger button? */
6
- actionType: BTNActionType;
6
+ actionType?: BTNActionType;
7
7
  /** How lg should the button be? - Defaults to 'base'. */
8
8
  size?: "xs" | "sm" | "md" | "lg" | "xl";
9
9
  /** The Button's text content. */
10
10
  label: string;
11
11
  /** The Icon to be displayed inside the button. */
12
- icon?: IDynamicIconProps;
12
+ icon?: IDynamicIconProps | UnifiedIconName;
13
13
  /** Does the button width grow to fill it's container? */
14
14
  fullWidth?: boolean;
15
15
  /** Optionally render as anchor tag */
@@ -25,9 +25,11 @@ export interface IButtonProps extends React.ComponentPropsWithoutRef<"button"> {
25
25
  /** Is the associated content loading? */
26
26
  isLoading?: boolean;
27
27
  className?: string;
28
+ iconObj?: React.ReactNode;
29
+ ref?: React.LegacyRef<HTMLButtonElement>;
28
30
  }
29
31
  /**
30
32
  * Primary UI component for user interaction
31
33
  */
32
- declare const Button: ({ actionType, size, label, icon, CustomSVGIcon, fullWidth, iconPosition, asLink, isLoading, className, ...props }: IButtonProps) => import("react/jsx-runtime").JSX.Element;
34
+ declare const Button: ({ actionType, size, label, icon, iconObj, CustomSVGIcon, fullWidth, iconPosition, asLink, isLoading, className, ref, ...props }: IButtonProps) => import("react/jsx-runtime").JSX.Element;
33
35
  export default Button;
@@ -6,6 +6,7 @@ type Story = StoryObj<typeof Button>;
6
6
  export declare const Danger: Story;
7
7
  export declare const DangerTrailingIcon: Story;
8
8
  export declare const DangerLeadingIcon: Story;
9
+ export declare const DangerSimpleIconName: Story;
9
10
  export declare const DangerExtraSmall: Story;
10
11
  export declare const DangerSmall: Story;
11
12
  export declare const DangerMedium: Story;
@@ -5,6 +5,7 @@ export default meta;
5
5
  type Story = StoryObj<typeof Button>;
6
6
  export declare const Primary: Story;
7
7
  export declare const PrimaryLeadingIcon: Story;
8
+ export declare const PrimarySimpleIconName: Story;
8
9
  export declare const PrimaryTrailingIcon: Story;
9
10
  export declare const PrimaryExtraSmall: Story;
10
11
  export declare const PrimarySmall: Story;
@@ -6,6 +6,7 @@ type Story = StoryObj<typeof Button>;
6
6
  export declare const Secondary: Story;
7
7
  export declare const SecondaryTrailingIcon: Story;
8
8
  export declare const SecondaryLeadingIcon: Story;
9
+ export declare const SecondarySimpleIconName: Story;
9
10
  export declare const SecondaryExtraSmall: Story;
10
11
  export declare const SecondarySmall: Story;
11
12
  export declare const SecondaryMedium: Story;
@@ -23,6 +23,8 @@ export interface IInputFieldProps extends React.ComponentPropsWithoutRef<"input"
23
23
  required?: boolean;
24
24
  /** use input psuedo classes for :valid and :invalid styles. on by default */
25
25
  clientSideCheck?: boolean;
26
+ /**ref for input */
27
+ ref?: React.Ref<HTMLInputElement>;
26
28
  }
27
29
  declare const InputField: React.FC<IInputFieldProps>;
28
30
  export default InputField;
@@ -1,14 +1,10 @@
1
1
  import React, { HTMLAttributes } from "react";
2
2
  import { Placement } from "@floating-ui/react";
3
3
  import { ClassNameWithAutocomplete } from "utils/types";
4
- import { IDynamicIconProps } from "@/stories/atoms/icons";
4
+ import { IDynamicIconProps, UnifiedIconName } from "@/stories/atoms/icons";
5
5
  export interface IItemProp extends HTMLAttributes<HTMLButtonElement> {
6
- icon?: {
7
- name: IDynamicIconProps["icon"];
8
- className?: ClassNameWithAutocomplete;
9
- pos?: "trailing" | "leading";
10
- outline?: boolean;
11
- };
6
+ icon?: IDynamicIconProps | UnifiedIconName;
7
+ iconPosition?: "trailing" | "leading";
12
8
  label: string;
13
9
  onClick?(): void;
14
10
  isEmphasized?: boolean;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agility/plenum-ui",
3
- "version": "2.0.0-rc2",
3
+ "version": "2.0.0-rc4",
4
4
  "license": "MIT",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.js",
@@ -63,4 +63,4 @@
63
63
  "tailwindcss": "^3.2.4",
64
64
  "typescript": "^5.1.6"
65
65
  }
66
- }
66
+ }
@@ -41,6 +41,13 @@ export const AlternativeExtraSmall: Story = {
41
41
  size: "sm"
42
42
  }
43
43
  }
44
+ export const AlternativeSimpleIconName: Story = {
45
+ args: {
46
+ ...Alternative.args,
47
+ icon: defaultIcon.icon,
48
+ iconPosition: "leading"
49
+ }
50
+ }
44
51
  export const AlternativeSmall: Story = {
45
52
  args: {
46
53
  ...Alternative.args,
@@ -8,13 +8,13 @@ import { DynamicIcon, UnifiedIconName, IDynamicIconProps } from "../../icons"
8
8
  export type BTNActionType = "primary" | "secondary" | "alternative" | "danger"
9
9
  export interface IButtonProps extends React.ComponentPropsWithoutRef<"button"> {
10
10
  /** Is the button a Primary CTA, alternative or danger button? */
11
- actionType: BTNActionType
11
+ actionType?: BTNActionType
12
12
  /** How lg should the button be? - Defaults to 'base'. */
13
13
  size?: "xs" | "sm" | "md" | "lg" | "xl"
14
14
  /** The Button's text content. */
15
15
  label: string
16
16
  /** The Icon to be displayed inside the button. */
17
- icon?: IDynamicIconProps
17
+ icon?: IDynamicIconProps | UnifiedIconName
18
18
  /** Does the button width grow to fill it's container? */
19
19
  fullWidth?: boolean
20
20
  /** Optionally render as anchor tag */
@@ -30,6 +30,8 @@ export interface IButtonProps extends React.ComponentPropsWithoutRef<"button"> {
30
30
  /** Is the associated content loading? */
31
31
  isLoading?: boolean
32
32
  className?: string
33
+ iconObj?: React.ReactNode
34
+ ref?: React.LegacyRef<HTMLButtonElement>
33
35
  }
34
36
  /**
35
37
  * Primary UI component for user interaction
@@ -39,12 +41,14 @@ const Button = ({
39
41
  size = "sm",
40
42
  label,
41
43
  icon,
44
+ iconObj,
42
45
  CustomSVGIcon,
43
46
  fullWidth = false,
44
47
  iconPosition = "trailing",
45
48
  asLink,
46
49
  isLoading = false,
47
50
  className,
51
+ ref,
48
52
  ...props
49
53
  }: IButtonProps) => {
50
54
  const iconStyles = cn(
@@ -88,6 +92,7 @@ const Button = ({
88
92
  },
89
93
  className ? className : ""
90
94
  )}
95
+ ref={ref}
91
96
  {...props}
92
97
  >
93
98
  {CustomSVGIcon &&
@@ -96,11 +101,20 @@ const Button = ({
96
101
  ) : (
97
102
  <i>{CustomSVGIcon}</i>
98
103
  ))}
104
+ {iconObj &&
105
+ iconPosition === "leading" &&
106
+ (!isLoading ? (
107
+ <>{iconObj}</>
108
+ ) : (
109
+ <div className={cn("h-4 rounded-full w-4 border-2 m-0 animate-spin", loaderColors, loaderSize)} />
110
+ ))}
99
111
 
100
112
  {icon &&
101
113
  iconPosition === "leading" &&
102
114
  (isLoading ? (
103
115
  <div className={cn("h-4 rounded-full w-4 border-2 m-0 animate-spin", loaderColors, loaderSize)} />
116
+ ) : typeof icon === "string" ? (
117
+ <DynamicIcon {...{ icon: icon, className: iconStyles }} />
104
118
  ) : (
105
119
  <DynamicIcon {...{ ...icon, className: iconStyles }} />
106
120
  ))}
@@ -113,9 +127,18 @@ const Button = ({
113
127
  iconPosition === "trailing" &&
114
128
  (isLoading ? (
115
129
  <div className={cn("h-4 rounded-full w-4 border-2 m-0 animate-spin", loaderColors, loaderSize)} />
130
+ ) : typeof icon === "string" ? (
131
+ <DynamicIcon {...{ icon: icon, className: iconStyles }} />
116
132
  ) : (
117
133
  <DynamicIcon {...{ ...icon, className: iconStyles }} />
118
134
  ))}
135
+ {iconObj &&
136
+ iconPosition === "trailing" &&
137
+ (!isLoading ? (
138
+ <>{iconObj}</>
139
+ ) : (
140
+ <div className={cn("h-4 rounded-full w-4 border-2 m-0 animate-spin", loaderColors, loaderSize)} />
141
+ ))}
119
142
  </button>
120
143
  )
121
144
  }
@@ -38,6 +38,13 @@ export const DangerLeadingIcon: Story = {
38
38
  iconPosition: "leading"
39
39
  }
40
40
  }
41
+ export const DangerSimpleIconName: Story = {
42
+ args: {
43
+ ...Danger.args,
44
+ icon: "TrashIcon",
45
+ iconPosition: "leading"
46
+ }
47
+ }
41
48
  export const DangerExtraSmall: Story = {
42
49
  args: {
43
50
  ...Danger.args,
@@ -39,6 +39,13 @@ export const PrimaryLeadingIcon: Story = {
39
39
  iconPosition: "leading"
40
40
  }
41
41
  }
42
+ export const PrimarySimpleIconName: Story = {
43
+ args: {
44
+ ...Primary.args,
45
+ icon: "CheckIcon",
46
+ iconPosition: "leading"
47
+ }
48
+ }
42
49
  export const PrimaryTrailingIcon: Story = {
43
50
  args: {
44
51
  ...PrimaryLeadingIcon.args,
@@ -41,6 +41,13 @@ export const SecondaryLeadingIcon: Story = {
41
41
  iconPosition: "leading"
42
42
  }
43
43
  }
44
+ export const SecondarySimpleIconName: Story = {
45
+ args: {
46
+ ...Secondary.args,
47
+ icon: "BellIcon",
48
+ iconPosition: "leading"
49
+ }
50
+ }
44
51
  export const SecondaryExtraSmall: Story = {
45
52
  args: {
46
53
  ...Secondary.args,
@@ -0,0 +1,18 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import InputCounter, { IInputCounterProps } from "./InputCounter"
3
+
4
+ const meta: Meta<typeof InputCounter> = {
5
+ title: "Design System/molecules/inputs/InputCounter",
6
+ component: InputCounter,
7
+ tags: ["autodocs"],
8
+ argTypes: {}
9
+ }
10
+
11
+ export default meta
12
+ type Story = StoryObj<typeof InputCounter>
13
+ export const DefaultInputCounterStory: Story = {
14
+ args: {
15
+ limit: 100,
16
+ current: 0
17
+ } as IInputCounterProps
18
+ }
@@ -0,0 +1,24 @@
1
+ import React, { FC } from "react"
2
+
3
+ export interface IInputCounterProps {
4
+ /** Counter limit */
5
+ limit: number | undefined
6
+ /** Counter current number */
7
+ current: number
8
+ }
9
+
10
+ /** Primary UI component for user interaction */
11
+ const InputCounter: FC<IInputCounterProps> = ({ current = 0, limit }) => {
12
+ return (
13
+ <div className="mt-1 text-center text-sm text-gray-500 flex gap-1">
14
+ <div className="currentCount">{current}</div>
15
+ {(limit || 0) > 0 && (
16
+ <>
17
+ <div>/</div>
18
+ <div className="limitCount">{limit}</div>
19
+ </>
20
+ )}
21
+ </div>
22
+ )
23
+ }
24
+ export default InputCounter
@@ -0,0 +1,3 @@
1
+
2
+ export { default } from './InputCounter';
3
+ export * from './InputCounter';
@@ -37,7 +37,8 @@ export interface IInputFieldProps extends React.ComponentPropsWithoutRef<"input"
37
37
  required?: boolean
38
38
  /** use input psuedo classes for :valid and :invalid styles. on by default */
39
39
  clientSideCheck?: boolean
40
- /** use placeholder string */
40
+ /**ref for input */
41
+ ref?: React.Ref<HTMLInputElement>
41
42
  }
42
43
 
43
44
  const InputField: React.FC<IInputFieldProps> = ({
@@ -54,16 +55,20 @@ const InputField: React.FC<IInputFieldProps> = ({
54
55
  clientSideCheck = true,
55
56
  placeholder,
56
57
  className,
58
+ ref,
57
59
  ...rest
58
60
  }) => {
61
+
59
62
  return (
60
63
  <input
61
64
  {...{
62
65
  type,
63
66
  id,
67
+ ref,
64
68
  name,
65
69
  value,
66
70
  onChange: (e) => {
71
+ console.log(e)
67
72
  handleChange(e.target.value)
68
73
  },
69
74
  autoFocus: isFocused,
@@ -0,0 +1,32 @@
1
+ import type { Meta, StoryObj } from "@storybook/react"
2
+ import TextInput, { ITextInputProps } from "./TextInput"
3
+ import React from "react"
4
+
5
+ const meta: Meta<typeof TextInput> = {
6
+ title: "Design System/molecules/inputs/TextInput",
7
+ component: TextInput,
8
+ tags: ["autodocs"],
9
+ argTypes: {}
10
+ }
11
+
12
+ export default meta
13
+ type Story = StoryObj<typeof TextInput>
14
+
15
+ export const DefaultTextInputStory: Story = {
16
+ args: {
17
+ type: "text",
18
+ label: "Label",
19
+ isFocused: true,
20
+ isError: false,
21
+ id: "id",
22
+ name: "name",
23
+ isRequired: true,
24
+
25
+ isDisabled: false,
26
+ isReadonly: false,
27
+ message: "message",
28
+ isShowCounter: true,
29
+ maxLength: 100,
30
+ placeholder: "placeholder"
31
+ }
32
+ }
@@ -0,0 +1,162 @@
1
+ import React, { forwardRef, useEffect, useId, useRef, useState } from "react"
2
+ import { default as cn } from "classnames"
3
+ import InputLabel from "../InputLabel"
4
+ import InputField, { AcceptedInputTypes } from "../InputField"
5
+ import InputCounter from "../InputCounter"
6
+
7
+ export interface ITextInputProps {
8
+ /** Input type*/
9
+ type: AcceptedInputTypes
10
+ /** Input ID */
11
+ id?: string
12
+ /** Input Name */
13
+ name?: string
14
+ /** Label for the input */
15
+ label?: string
16
+ /** Force the focus state on the input */
17
+ isFocused?: boolean
18
+ /** Error state */
19
+ isError?: boolean
20
+ /** If field is required */
21
+ isRequired?: boolean
22
+ /** Disabled state */
23
+ isDisabled?: boolean
24
+ /** Readonly state */
25
+ isReadonly?: boolean
26
+ /** Set default value */
27
+ defaultValue?: string
28
+ /** Message shown under the text field */
29
+ message?: string
30
+ /** Input character counter */
31
+ isShowCounter?: boolean
32
+ /** Max length of input character */
33
+ maxLength?: number
34
+ /** Callback on change */
35
+ onChange(value: string): void
36
+ /** input value */
37
+ value: string
38
+ /**Placeholder input text*/
39
+ placeholder?: string
40
+
41
+ className?: string
42
+ }
43
+
44
+ const TextInput = (
45
+ {
46
+ label,
47
+ isFocused,
48
+ isError,
49
+ id,
50
+ name,
51
+ isRequired,
52
+ type,
53
+ defaultValue,
54
+ isDisabled,
55
+ isReadonly,
56
+ message,
57
+ isShowCounter,
58
+ maxLength,
59
+ onChange,
60
+ placeholder,
61
+ value: externalValue,
62
+ className,
63
+ ...props
64
+ }: ITextInputProps,
65
+ ref: React.Ref<HTMLInputElement>
66
+ ) => {
67
+ const uniqueID = useId()
68
+ const [isFocus, setIsFocus] = useState<boolean>(Boolean(isFocused))
69
+
70
+ const [value, setValue] = useState<string>(externalValue || defaultValue || "")
71
+ const inputRef = useRef<HTMLInputElement>(null)
72
+
73
+ useEffect(() => {
74
+ //if the external value is updated by the parent component, reset the value in here...
75
+ if (externalValue !== undefined && externalValue !== null) {
76
+ setValue(externalValue)
77
+ }
78
+ }, [externalValue])
79
+
80
+ // set force focus
81
+ useEffect(() => {
82
+ const input = inputRef.current
83
+ if (!input || isFocus === undefined || isDisabled) return
84
+ if (isFocus) {
85
+ input.focus()
86
+ } else {
87
+ input.blur()
88
+ }
89
+ }, [isFocus])
90
+
91
+ // set label as active if default value is set
92
+ useEffect(() => {
93
+ const input = inputRef.current
94
+ if (!input || defaultValue === undefined || defaultValue === "") return
95
+ }, [defaultValue])
96
+
97
+ const handleInputFocus = () => setIsFocus(true)
98
+ // add other focus effects here
99
+
100
+ const handleInputBlur = () => setIsFocus(false)
101
+
102
+ if (!id) id = `input-${uniqueID}`
103
+ if (!name) name = id
104
+
105
+ return (
106
+ <div className="relative">
107
+ <InputLabel
108
+ isPlaceholder={true}
109
+ label={label}
110
+ isRequired={isRequired}
111
+ id={id}
112
+ isError={isError}
113
+ isActive={true}
114
+ isDisabled={isDisabled}
115
+ />
116
+ <InputField
117
+ onFocus={handleInputFocus}
118
+ onBlur={handleInputBlur}
119
+ handleChange={(v) => setValue(v)}
120
+ ref={ref}
121
+ type={type}
122
+ name={name}
123
+ id={id}
124
+ className={cn(
125
+ "w-full rounded border py-2 px-3 text-sm font-normal leading-5",
126
+ { "border-gray-300": !isFocus && !isError },
127
+ {
128
+ "!border-purple-500 shadow-none outline-purple-500 focus:!ring-purple-500": isFocus && !isError
129
+ },
130
+ {
131
+ "!border-red-500 shadow-none focus:ring-red-500": isError
132
+ },
133
+ className
134
+ )}
135
+ isDisabled={isDisabled}
136
+ isReadonly={isReadonly}
137
+ value={value}
138
+ defaultValue={defaultValue}
139
+ maxLength={maxLength}
140
+ placeholder={placeholder}
141
+ {...props}
142
+ />
143
+ <div className="flex flex-row space-x-3">
144
+ <div className="grow">
145
+ {message && (
146
+ <span className={cn("mt-1 block text-sm", isError ? "text-red-500" : "text-gray-500")}>
147
+ {message}
148
+ </span>
149
+ )}
150
+ </div>
151
+ {isShowCounter && (
152
+ <div className="shrink-0">
153
+ <InputCounter current={Number(value?.length)} limit={maxLength} />
154
+ </div>
155
+ )}
156
+ </div>
157
+ </div>
158
+ )
159
+ }
160
+
161
+ const _TextInput = forwardRef<HTMLInputElement, ITextInputProps>(TextInput)
162
+ export default _TextInput
@@ -0,0 +1,3 @@
1
+
2
+ export { default } from './TextInput';
3
+ export * from './TextInput';