+
diff --git a/docs/blocks/authentication/LoginBasic.tsx b/docs/blocks/authentication/LoginBasic.tsx
new file mode 100644
index 00000000..2d00bdac
--- /dev/null
+++ b/docs/blocks/authentication/LoginBasic.tsx
@@ -0,0 +1,117 @@
+import { Block } from '../../../src/layout/Block';
+import { Button } from '../../../src/elements/Button';
+import { Card } from '../../../src/layout/Card';
+import { Divider } from '../../../src/layout';
+import { Input } from '../../../src/form/Input';
+import { motion } from 'framer-motion';
+import { useForm, Controller } from 'react-hook-form';
+
+import logo from '../../assets/reaviz.svg';
+
+export const LoginBasic = () => {
+ const {
+ control,
+ handleSubmit,
+ formState: { isSubmitting }
+ } = useForm();
+
+ return (
+
+
+
+
![Logo]({logo})
+
+ Log In or create account
+
+
+ Welcome to Reablocks, powered by Good Code
+
+
+
+
+
+ );
+};
diff --git a/docs/blocks/authentication/LoginFull.tsx b/docs/blocks/authentication/LoginFull.tsx
new file mode 100644
index 00000000..3e8ca7ea
--- /dev/null
+++ b/docs/blocks/authentication/LoginFull.tsx
@@ -0,0 +1,167 @@
+import { Block } from '../../../src/layout/Block';
+import { Button } from '../../../src/elements/Button';
+import { Card } from '../../../src/layout/Card';
+import { Divider } from '../../../src/layout/Divider';
+import { Input } from '../../../src/form/Input';
+import { Stack } from '../../../src/layout/Stack';
+import { motion } from 'framer-motion';
+import { useForm, Controller } from 'react-hook-form';
+
+import logo from '../../assets/reaviz.svg';
+import bg from '../../assets/bg.png';
+
+export const LoginFull = () => {
+ const {
+ control,
+ handleSubmit,
+ formState: { isSubmitting }
+ } = useForm();
+
+ return (
+
+
+
+
![Logo]({logo})
+
+ Log In or create account
+
+
+ Welcome to Reablocks, powered by Good Code
+
+
+
+
+
+
![]({bg})
+
+
+ );
+};
diff --git a/docs/blocks/authentication/LoginPassword.tsx b/docs/blocks/authentication/LoginPassword.tsx
new file mode 100644
index 00000000..73a2d36c
--- /dev/null
+++ b/docs/blocks/authentication/LoginPassword.tsx
@@ -0,0 +1,132 @@
+import { Block } from '../../../src/layout/Block';
+import { Button } from '../../../src/elements/Button';
+import { Card } from '../../../src/layout/Card';
+import { Checkbox } from '../../../src/form/Checkbox';
+import { Divider } from '../../../src/layout/Divider';
+import { Input } from '../../../src/form/Input';
+import { Stack } from '../../../src/layout/Stack';
+import { motion } from 'framer-motion';
+import { useForm, Controller } from 'react-hook-form';
+
+import logo from '../../assets/reaviz.svg';
+
+export const LoginPassword = () => {
+ const {
+ control,
+ handleSubmit,
+ setValue,
+ formState: { isSubmitting }
+ } = useForm();
+
+ return (
+
+
+
+
![Logo]({logo})
+
+ Log In or create account
+
+
+ Welcome to Reablocks, powered by Good Code
+
+
+
+
+
+ );
+};
diff --git a/docs/blocks/authentication/LoginSocial.tsx b/docs/blocks/authentication/LoginSocial.tsx
new file mode 100644
index 00000000..44c946ee
--- /dev/null
+++ b/docs/blocks/authentication/LoginSocial.tsx
@@ -0,0 +1,160 @@
+import { Block } from '../../../src/layout/Block';
+import { Button } from '../../../src/elements/Button';
+import { Card } from '../../../src/layout/Card';
+import { Divider } from '../../../src/layout/Divider';
+import { Input } from '../../../src/form/Input';
+import { Stack } from '../../../src/layout/Stack';
+import { motion } from 'framer-motion';
+import { useForm, Controller } from 'react-hook-form';
+
+import logo from '../../assets/reaviz.svg';
+
+export const LoginSocial = () => {
+ const {
+ control,
+ handleSubmit,
+ formState: { isSubmitting }
+ } = useForm();
+
+ return (
+
+
+
+
![Logo]({logo})
+
+ Log In or create account
+
+
+ Welcome to Reablocks, powered by Good Code
+
+
+
+
+
+ );
+};
diff --git a/docs/components/layers/Notification.mdx b/docs/components/layers/Notification.mdx
index 3a55ffe4..35ad8910 100644
--- a/docs/components/layers/Notification.mdx
+++ b/docs/components/layers/Notification.mdx
@@ -46,7 +46,45 @@ const Component = () => {
};
```
-## Example
+Use these `Notifications` props to customize your notifications at a global level
+
+### API
+
+
+## NotificationOptions
+Pass in options when calling the `notify` function to customize the notification.
+
+```tsx
+const options: NotificationOptions = {
+ body: "This is the body of the notification",
+ timeout: 5000,
+}
+
+notify("This is a notification", options)
+```
+
+### API
+```jsx
+type NotificationVariants =
+ | 'default'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'info';
+
+interface NotificationOptions {
+ title?: string | React.JSX.Element | React.JSX.Element[];
+ body?: string | React.JSX.Element | React.JSX.Element[];
+ timeout?: number;
+ showClose?: boolean;
+ variant?: NotificationVariants;
+ className?: string;
+ icon?: string | React.JSX.Element | React.JSX.Element[];
+ action?: string | React.JSX.Element | React.JSX.Element[];
+}
+```
+
+## Examples
### Notification for interface essential scenarios
### Custom styled notitications
@@ -60,6 +98,3 @@ This component uses the following default theme:
Learn more about how to customize in the [Theme documentation](?path=/docs/docs-theme-getting-started--docs).
-
-## API
-
diff --git a/package.json b/package.json
index 920440f2..cb20e617 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "reablocks",
- "version": "7.3.4",
+ "version": "7.3.7",
"description": "Component library for React",
"scripts": {
"build": "npm run build:js && npm run build:styles",
diff --git a/src/assets/icons/check_circle.svg b/src/assets/icons/check_circle.svg
new file mode 100644
index 00000000..ae765629
--- /dev/null
+++ b/src/assets/icons/check_circle.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/error_circle.svg b/src/assets/icons/error_circle.svg
new file mode 100644
index 00000000..37bd7274
--- /dev/null
+++ b/src/assets/icons/error_circle.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/info.svg b/src/assets/icons/info.svg
new file mode 100644
index 00000000..f2b1ae08
--- /dev/null
+++ b/src/assets/icons/info.svg
@@ -0,0 +1,12 @@
+
\ No newline at end of file
diff --git a/src/assets/icons/warning.svg b/src/assets/icons/warning.svg
new file mode 100644
index 00000000..d832f93b
--- /dev/null
+++ b/src/assets/icons/warning.svg
@@ -0,0 +1,5 @@
+
\ No newline at end of file
diff --git a/src/elements/Avatar/Avatar.story.tsx b/src/elements/Avatar/Avatar.story.tsx
index 279e1e88..82f5c7fd 100644
--- a/src/elements/Avatar/Avatar.story.tsx
+++ b/src/elements/Avatar/Avatar.story.tsx
@@ -16,6 +16,14 @@ Default.args = {
rounded: false
};
+export const Outline = Template.bind({});
+Outline.args = {
+ name: 'John Doe',
+ size: 50,
+ rounded: false,
+ variant: 'outline'
+};
+
export const RoundedWithImage = Template.bind({});
RoundedWithImage.args = {
src: 'https://goodcode.us/static/austin-d1a2c5249336c31662b8ee6d4e169b2b.jpg',
diff --git a/src/elements/Avatar/Avatar.tsx b/src/elements/Avatar/Avatar.tsx
index 08e6cb16..7d209370 100644
--- a/src/elements/Avatar/Avatar.tsx
+++ b/src/elements/Avatar/Avatar.tsx
@@ -21,6 +21,11 @@ export interface AvatarProps extends React.HTMLAttributes
{
*/
size?: number;
+ /**
+ * Style variant for the avatar.
+ */
+ variant?: 'filled' | 'outline';
+
/**
* Whether the avatar is rounded.
*/
@@ -53,6 +58,7 @@ export const Avatar = React.forwardRef(
src,
color,
size,
+ variant,
rounded,
className,
colorOptions,
@@ -88,7 +94,12 @@ export const Avatar = React.forwardRef(
height: `${size}px`,
fontSize: `${fontSize}px`,
backgroundImage: src ? `url(${src})` : 'none',
- backgroundColor
+ backgroundColor,
+ ...(variant === 'outline' && {
+ backgroundColor: 'transparent',
+ border: `solid 1px ${backgroundColor}`,
+ color: backgroundColor
+ })
}}
ref={ref}
>
@@ -100,5 +111,6 @@ export const Avatar = React.forwardRef(
Avatar.defaultProps = {
size: 24,
- rounded: true
+ rounded: true,
+ variant: 'filled'
};
diff --git a/src/form/Input/InlineInput/InlineInput.tsx b/src/form/Input/InlineInput/InlineInput.tsx
index 9551fe9e..efc22f1e 100644
--- a/src/form/Input/InlineInput/InlineInput.tsx
+++ b/src/form/Input/InlineInput/InlineInput.tsx
@@ -1,6 +1,5 @@
-import React, { FC, forwardRef, Ref, InputHTMLAttributes } from 'react';
+import { FC, forwardRef, Ref, InputHTMLAttributes } from 'react';
import AutosizeInput from 'react-18-input-autosize';
-import { InputRef } from '../Input';
import { twMerge } from 'tailwind-merge';
import { InputTheme } from '../InputTheme';
import { useComponentTheme } from '../../../utils';
@@ -46,7 +45,7 @@ export const InlineInput: FC = forwardRef(
theme: customTheme,
...rest
},
- ref: Ref
+ ref: Ref
) => {
const theme: InputTheme = useComponentTheme('input', customTheme);
diff --git a/src/form/Select/Select.tsx b/src/form/Select/Select.tsx
index 7ab135a5..1072b1a4 100644
--- a/src/form/Select/Select.tsx
+++ b/src/form/Select/Select.tsx
@@ -203,6 +203,16 @@ export interface SelectProps {
* The options for the Fuse.js search algorithm.
*/
searchOptions?: Fuse.IFuseOptions;
+
+ /**
+ * When menu is opened
+ */
+ onOpenMenu?: () => void;
+
+ /**
+ * When menu is closed
+ */
+ onCloseMenu?: () => void;
}
export const Select: FC> = ({
@@ -239,7 +249,9 @@ export const Select: FC> = ({
onInputKeyUp,
onOptionsChange,
onInputChange,
- searchOptions
+ searchOptions,
+ onOpenMenu,
+ onCloseMenu
}) => {
const overlayRef = useRef(null);
const inputRef = useRef(null);
@@ -651,7 +663,8 @@ export const Select: FC> = ({
}
resetSelect();
- }, [createable, keyword, resetSelect, toggleSelectedOption]);
+ onCloseMenu?.();
+ }, [createable, keyword, onCloseMenu, resetSelect, toggleSelectedOption]);
return (
> = ({
reference={inputRef?.current?.containerRef}
ref={overlayRef}
onClose={onOverlayClose}
+ onOpen={onOpenMenu}
content={() => (
element={menu}
diff --git a/src/layers/Notification/Notification.story.tsx b/src/layers/Notification/Notification.story.tsx
index 41b1f558..df90193f 100644
--- a/src/layers/Notification/Notification.story.tsx
+++ b/src/layers/Notification/Notification.story.tsx
@@ -101,6 +101,7 @@ export const Variants = () => (
notifySuccess,
notifyError,
notifyWarning,
+ notifyInfo,
clearAllNotifications: clearAll
}) => (
@@ -125,6 +126,12 @@ export const Variants = () => (
>
Warning
+
@@ -157,6 +164,83 @@ export const FloodPrevention = () => (
);
+export const CustomIcon = () => (
+
+
+ {({ notify, clearAllNotifications: clearAll }) => (
+
+
+
+
+
+
+ )}
+
+
+);
+
export const CustomComponent = () => (
(
);
+
+export const WithAction = () => (
+
+
+ {({ notify, clearAllNotifications }) => (
+
+
+
+
+ clearAllNotifications()}>Clear
+
+ )}
+
+
+);
diff --git a/src/layers/Notification/Notification.tsx b/src/layers/Notification/Notification.tsx
index 39ad5146..dbaf264e 100644
--- a/src/layers/Notification/Notification.tsx
+++ b/src/layers/Notification/Notification.tsx
@@ -20,6 +20,8 @@ export const Notification: FC = ({
timeout,
className,
variant,
+ icon,
+ action,
component,
onClose,
theme: customTheme
@@ -57,12 +59,23 @@ export const Notification: FC = ({
- {title &&
{title}
}
+ {title && (
+
+ {icon && (
+
+ {icon}
+
+ )}
+ {title}
+
+ )}
{body && (
{typeof body === 'string' ? (
@@ -73,6 +86,7 @@ export const Notification: FC = ({
)}
+ {action &&
{action}
}
{showClose && (
;
};
+ icons?: {
+ [variant in NotificationVariants]?:
+ | string
+ | React.JSX.Element
+ | React.JSX.Element[];
+ };
theme?: NotificationTheme;
}
@@ -48,6 +59,7 @@ export const Notifications: FC = ({
className,
preventFlooding,
components,
+ icons,
theme: customTheme
}) => {
const [notifications, setNotifications] = useState([]);
@@ -75,6 +87,7 @@ export const Notifications: FC = ({
id,
variant: 'default',
timeout,
+ icon: icons?.default,
showClose,
...options
};
@@ -89,25 +102,39 @@ export const Notifications: FC = ({
return sorted;
});
},
- [limit, preventFlooding, showClose, timeout]
+ [icons?.default, limit, preventFlooding, showClose, timeout]
);
const notifyError = useCallback(
(title: string, options: NotificationOptions = {}) =>
- notify(title, { ...options, variant: 'error' }),
- [notify]
+ notify(title, {
+ variant: 'error',
+ icon: icons?.error,
+ ...options
+ }),
+ [icons?.error, notify]
);
const notifyWarning = useCallback(
(title: string, options: NotificationOptions = {}) =>
- notify(title, { ...options, variant: 'warning' }),
- [notify]
+ notify(title, { variant: 'warning', icon: icons?.warning, ...options }),
+ [icons?.warning, notify]
);
const notifySuccess = useCallback(
(title: string, options: NotificationOptions = {}) =>
- notify(title, { ...options, variant: 'success' }),
- [notify]
+ notify(title, {
+ variant: 'success',
+ icon: icons?.success,
+ ...options
+ }),
+ [icons?.success, notify]
+ );
+
+ const notifyInfo = useCallback(
+ (title: string, options: NotificationOptions = {}) =>
+ notify(title, { variant: 'info', icon: icons?.info, ...options }),
+ [icons?.info, notify]
);
const values = useMemo(
@@ -116,6 +143,7 @@ export const Notifications: FC = ({
notifyError,
notifyWarning,
notifySuccess,
+ notifyInfo,
clearNotification,
clearAllNotifications
}),
@@ -125,7 +153,8 @@ export const Notifications: FC = ({
notify,
notifyError,
notifySuccess,
- notifyWarning
+ notifyWarning,
+ notifyInfo
]
);
@@ -190,5 +219,12 @@ Notifications.defaultProps = {
limit: 10,
timeout: 4000,
showClose: true,
- preventFlooding: true
+ preventFlooding: true,
+ icons: {
+ default: ,
+ success: ,
+ warning: ,
+ error: ,
+ info:
+ }
};
diff --git a/src/layers/Notification/NotificationsContext.tsx b/src/layers/Notification/NotificationsContext.tsx
index a625a730..28b04302 100644
--- a/src/layers/Notification/NotificationsContext.tsx
+++ b/src/layers/Notification/NotificationsContext.tsx
@@ -1,6 +1,11 @@
import React, { createContext } from 'react';
-export type NotificationVariants = 'default' | 'success' | 'warning' | 'error';
+export type NotificationVariants =
+ | 'default'
+ | 'success'
+ | 'warning'
+ | 'error'
+ | 'info';
export interface NotificationOptions {
title?: string | React.JSX.Element | React.JSX.Element[];
@@ -9,6 +14,8 @@ export interface NotificationOptions {
showClose?: boolean;
variant?: NotificationVariants;
className?: string;
+ icon?: string | React.JSX.Element | React.JSX.Element[];
+ action?: string | React.JSX.Element | React.JSX.Element[];
}
export interface NotificationsContextValue {
@@ -32,6 +39,11 @@ export interface NotificationsContextValue {
*/
notifySuccess(title: string, options?: NotificationOptions): void;
+ /**
+ * Create an info notification.
+ */
+ notifyInfo(title: string, options?: NotificationOptions): void;
+
/**
* Clear a specific notification.
*/
@@ -48,6 +60,7 @@ export const NotificationsContext = createContext({
notifyError: () => undefined,
notifyWarning: () => undefined,
notifySuccess: () => undefined,
+ notifyInfo: () => undefined,
clearNotification: () => undefined,
clearAllNotifications: () => undefined
});
diff --git a/src/layout/Tabs/Tab.tsx b/src/layout/Tabs/Tab.tsx
index b2c23dbc..864d6b71 100644
--- a/src/layout/Tabs/Tab.tsx
+++ b/src/layout/Tabs/Tab.tsx
@@ -43,11 +43,11 @@ export interface TabProps extends PropsWithChildren {
onSelect?: () => void;
/**
- * The variant of the tabs.
+ * The size of the tabs.
*
* @private
*/
- variant?: 'primary' | 'secondary';
+ size?: 'small' | 'medium' | 'large';
/**
* Theme for the Tabs.
@@ -63,7 +63,7 @@ export const Tab: FC = ({
className,
disabled,
onSelect,
- variant = 'primary',
+ size = 'medium',
theme: customTheme
}) => {
const theme: TabsTheme = useComponentTheme('tabs', customTheme);
@@ -78,7 +78,7 @@ export const Tab: FC = ({
[theme.list.tab.disabled]: disabled,
[theme.list.tab.selected]: selected
},
- theme.list.tab.variant?.[variant]?.button
+ theme.list.tab.size?.[size]
)}
disabled={disabled}
role="tab"
@@ -96,8 +96,8 @@ export const Tab: FC = ({
{selected && (
diff --git a/src/layout/Tabs/TabList.tsx b/src/layout/Tabs/TabList.tsx
index 15de41d0..7434dda0 100644
--- a/src/layout/Tabs/TabList.tsx
+++ b/src/layout/Tabs/TabList.tsx
@@ -41,6 +41,12 @@ export interface TabListProps extends PropsWithChildren {
*/
variant?: 'primary' | 'secondary';
+ /**
+ * The size of the tabs.
+ * @private
+ */
+ size?: 'small' | 'medium' | 'large';
+
/**
* Theme for the Tabs.
*/
@@ -55,6 +61,7 @@ export const TabList: FC = ({
selectedIndex,
onSelect,
variant = 'primary',
+ size = 'medium',
theme: customTheme
}) => {
const theme: TabsTheme = useComponentTheme('tabs', customTheme);
@@ -79,7 +86,7 @@ export const TabList: FC = ({
id={id}
selected={index === selectedIndex}
onSelect={() => onSelect(index)}
- variant={variant}
+ size={size}
>
{children}
diff --git a/src/layout/Tabs/Tabs.story.tsx b/src/layout/Tabs/Tabs.story.tsx
index d0979400..b79c3e4d 100644
--- a/src/layout/Tabs/Tabs.story.tsx
+++ b/src/layout/Tabs/Tabs.story.tsx
@@ -53,6 +53,41 @@ export const Variants = () => (
);
+export const Sizes = () => (
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ This is content for small tab 1
+ This is content for small tab 2
+ This is content for small tab 3
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ This is content for medium tab 1
+ This is content for medium tab 2
+ This is content for medium tab 3
+
+
+
+ Tab 1
+ Tab 2
+ Tab 3
+
+ This is content for large tab 1
+ This is content for large tab 2
+ This is content for large tab 3
+
+
+);
+
export const DefaultIndex = () => (
diff --git a/src/layout/Tabs/Tabs.tsx b/src/layout/Tabs/Tabs.tsx
index 7ddd9eba..1a0d9b15 100644
--- a/src/layout/Tabs/Tabs.tsx
+++ b/src/layout/Tabs/Tabs.tsx
@@ -46,6 +46,11 @@ export interface TabsProps extends PropsWithChildren {
*/
variant?: 'primary' | 'secondary';
+ /**
+ * The size of the tabs.
+ */
+ size?: 'small' | 'medium' | 'large';
+
/**
* The callback to be called when a tab is selected.
*/
@@ -62,6 +67,7 @@ export const Tabs: FC = ({
className,
style,
variant = 'primary',
+ size = 'medium',
direction = 'ltr',
defaultIndex = 0,
selectedIndex,
@@ -100,6 +106,7 @@ export const Tabs: FC = ({