Skip to content

Commit

Permalink
Merge pull request #31 from Arize-ai/29-drop
Browse files Browse the repository at this point in the history
#29 dropdown
  • Loading branch information
mikeldking authored Aug 24, 2021
2 parents 3d38961 + 7344cc0 commit a95d595
Show file tree
Hide file tree
Showing 25 changed files with 695 additions and 106 deletions.
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"version": "0.2.18",
"version": "0.2.20",
"license": "MIT",
"main": "dist/index.js",
"typings": "dist/index.d.ts",
Expand Down Expand Up @@ -51,11 +51,11 @@
"size-limit": [
{
"path": "dist/components.cjs.production.min.js",
"limit": "50 KB"
"limit": "70 KB"
},
{
"path": "dist/components.esm.js",
"limit": "50 KB"
"limit": "70 KB"
}
],
"devDependencies": {
Expand Down
67 changes: 0 additions & 67 deletions src/Button.tsx

This file was deleted.

5 changes: 2 additions & 3 deletions src/Spinner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,7 @@ const bounceDelay = keyframes`
}
`;

const spinner = css`
margin: 0 8px;
const spinnerCSS = css`
width: 16px;
height: 16px;
position: relative;
Expand Down Expand Up @@ -107,7 +106,7 @@ const spinner = css`

const Spinner = () => {
return (
<div css={spinner}>
<div css={spinnerCSS} className="ac-spinner">
<div className="loadingSpinnerCircle1 loadingChild"></div>
<div className="loadingSpinnerCircle2 loadingChild"></div>
<div className="loadingSpinnerCircle3 loadingChild"></div>
Expand Down
19 changes: 8 additions & 11 deletions src/ActionButton.tsx → src/button/ActionButton.tsx
Original file line number Diff line number Diff line change
@@ -1,16 +1,13 @@
import React, { ReactNode, CSSProperties } from 'react';
import { classNames } from './utils/classNames';
import React, { CSSProperties } from 'react';
import { classNames } from '../utils/classNames';
import { mergeProps } from '@react-aria/utils';
import { useButton } from '@react-aria/button';
import { useHover } from '@react-aria/interactions';
import { FocusableRef } from './types';
import { useFocusableRef } from './utils/useDOMRef';
import { FocusableRef } from '../types';
import { useFocusableRef } from '../utils/useDOMRef';
import { BaseButtonProps } from './types';

interface ButtonProps {
/** Whether the button is disabled. */
isDisabled?: boolean;
/** The content to display in the button. */
children?: ReactNode;
interface ActionButtonProps extends BaseButtonProps {
style?: CSSProperties;
}

Expand All @@ -21,7 +18,7 @@ interface ButtonProps {
* @returns
*/
function ActionButton(
props: ButtonProps,
props: ActionButtonProps,
ref: FocusableRef<HTMLButtonElement>
) {
let domRef = useFocusableRef(ref);
Expand All @@ -33,7 +30,7 @@ function ActionButton(
<button
{...mergeProps(buttonProps, hoverProps, otherProps)}
ref={domRef}
className={classNames('action-button', {
className={classNames('ac-action-button', {
'is-active': isPressed,
'is-disabled': isDisabled,
'is-hovered': isHovered,
Expand Down
127 changes: 127 additions & 0 deletions src/button/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import React, { ReactNode, SyntheticEvent } from 'react';
import { css } from '@emotion/core';
import theme from '../theme';
import Spinner from '../Spinner';
import { Text } from '../content';
import { FocusableRef } from '../types';
import { useFocusableRef } from '../utils/useDOMRef';
import { classNames } from '../utils/classNames';
import { mergeProps } from '@react-aria/utils';
import { useButton } from '@react-aria/button';
import { useHover } from '@react-aria/interactions';
import { BaseButtonProps } from './types';

const buttonCSS = css`
border: 1px solid ${theme.colors.dark1};
font-size: ${theme.typography.sizes.medium};
font-weight: 600;
display: flex;
justify-content: center;
align-items: center;
box-sizing: content-box;
border-radius: 4px;
&:not([disabled]) {
color: ${theme.textColors.white90};
transition: all 0.2s ease-in-out;
cursor: pointer;
}
&[disabled] {
color: ${theme.textColors.white70};
cursor: not-allowed;
}
&[data-size='normal'][data-childless='false'] {
padding: ${theme.spacing.padding8}px ${theme.spacing.padding16}px;
}
&[data-size='compact'][data-childless='false'] {
padding: ${theme.spacing.padding4}px ${theme.spacing.padding8}px;
}
&[data-size='normal'][data-childless='true'] {
padding: ${theme.spacing.padding8}px ${theme.spacing.padding8}px;
}
&[data-size='compact'][data-childless='true'] {
padding: ${theme.spacing.padding4}px ${theme.spacing.padding4}px;
}
&[data-variant='primary'] {
background-color: ${theme.colors.arizeBlue};
border-color: ${theme.components.button.primaryBorderColor};
&:hover:not([disabled]) {
background-color: ${theme.components.button.primaryHoverBackgroundColor};
}
}
&[data-variant='default'] {
background-color: ${theme.colors.gray500};
border-color: ${theme.components.button.defaultBorderColor};
&:hover:not([disabled]) {
background-color: ${theme.components.button.defaultHoverBackgroundColor};
}
}
&[data-childless='false'] > i,
& > .ac-spinner {
margin-right: ${theme.spacing.margin4}px;
}
`;

export interface ButtonProps extends BaseButtonProps {
children?: ReactNode | string;
variant: 'primary' | 'default';
disabled?: boolean;
className?: string;
onClick?: (e: SyntheticEvent<HTMLButtonElement>) => void;
loading?: boolean;
icon?: ReactNode;
/**
* The title of the button. Required if only showing an icon
*/
title?: string;
/**
* The size of the button
* @default 'normal'
*/
size?: 'normal' | 'compact';
}

const Button = (props: ButtonProps, ref: FocusableRef<HTMLButtonElement>) => {
let domRef = useFocusableRef(ref);
const {
disabled,
children,
loading,
variant,
onClick,
icon,
size = 'normal',
...otherProps
} = props;
const isDisabled = disabled || loading;
const isChildless = children == null;
const { buttonProps, isPressed } = useButton(props, domRef);
const { hoverProps, isHovered } = useHover({ isDisabled });

return (
<button
{...mergeProps(buttonProps, hoverProps, otherProps)}
ref={domRef}
css={buttonCSS}
className={classNames('ac-button', {
'is-active': isPressed,
'is-disabled': isDisabled,
'is-hovered': isHovered,
})}
data-variant={variant}
data-size={size}
data-childless={isChildless}
disabled={isDisabled}
onClick={isDisabled ? undefined : onClick}
>
{loading ? <Spinner /> : null}
{!loading && icon ? icon : null}
<Text textSize="medium" color={isDisabled ? 'white70' : 'white90'}>
{children}
</Text>
</button>
);
};

let _Button = React.forwardRef(Button);
export { _Button as Button };
27 changes: 27 additions & 0 deletions src/button/ButtonGroup.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import React, { ReactNode } from 'react';
import { css } from '@emotion/core';
import theme from '../theme';

type ButtonGroupProps = {
children: ReactNode;
};

/**
* ButtonGroup expects Buttons as children and should be used to manage button layouts
*/
export function ButtonGroup({ children }: ButtonGroupProps) {
return (
<div
className="ac-button-group"
css={css`
display: flex;
flex-direction: row;
& > .ac-button + .ac-button {
margin-left: ${theme.spacing.margin8}px;
}
`}
>
{children}
</div>
);
}
3 changes: 3 additions & 0 deletions src/button/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './Button';
export * from './ActionButton';
export * from './ButtonGroup';
8 changes: 8 additions & 0 deletions src/button/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import { ReactNode } from 'react';

export interface BaseButtonProps {
/** Whether the button is disabled. */
isDisabled?: boolean;
/** The content to display in the button. */
children?: ReactNode;
}
65 changes: 65 additions & 0 deletions src/dropdown/Dropdown.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
import React, { ReactNode, useState, useCallback, useRef } from 'react';
import { DropdownButton, DropdownButtonProps } from './DropdownButton';
import { DropdownMenu } from './DropdownMenu';
import { DropdownTrigger, DropdownTriggerProps } from './DropdownTrigger';
import { useResizeObserver } from '@react-aria/utils';
import { FocusableRefValue } from '../types';
import { useUnwrapDOMRef } from '../utils';

export type DropdownProps = {
/**
* The content of the dropdown menu
*/
menu: ReactNode;
/**
* the content of the dropdown button
*/
children: ReactNode;
/**
* the dropdown trigger props
*/
triggerProps?: Omit<DropdownTriggerProps, 'children'>;
/**
* the dropdown button props
*/
buttonProps?: DropdownButtonProps;
};

export function Dropdown({
menu,
children,
triggerProps = { placement: 'bottom left' },
buttonProps,
}: DropdownProps) {
let triggerRef = useRef<FocusableRefValue<HTMLButtonElement>>(null);
let unwrappedTriggerRef = useUnwrapDOMRef(triggerRef);

// Measure the width of the button to inform the width of the menu (below).
let [buttonWidth, setButtonWidth] = useState<number>();

let onResize = useCallback(() => {
if (unwrappedTriggerRef.current) {
let width = unwrappedTriggerRef.current.offsetWidth;
setButtonWidth(width);
}
}, [unwrappedTriggerRef, setButtonWidth]);

// Handle re-sizes of the trigger
useResizeObserver({
ref: unwrappedTriggerRef,
onResize: onResize,
});

const menuStyle = {
minWidth: buttonWidth,
};

return (
<DropdownTrigger {...triggerProps}>
<DropdownButton ref={triggerRef} {...buttonProps}>
{children}
</DropdownButton>
<DropdownMenu style={menuStyle}>{menu}</DropdownMenu>
</DropdownTrigger>
);
}
Loading

0 comments on commit a95d595

Please sign in to comment.