@a-type/ui 0.3.2 → 0.3.3
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/package.json +3 -2
- package/src/components/actions/ActionBar.tsx +38 -0
- package/src/components/actions/ActionButton.tsx +59 -0
- package/src/components/actions/index.ts +2 -0
- package/src/components/actions.ts +1 -0
- package/src/components/avatar/Avatar.tsx +62 -0
- package/src/components/avatar/AvatarList.tsx +71 -0
- package/src/components/avatar/index.ts +2 -0
- package/src/components/avatar.ts +1 -0
- package/src/components/button/Button.stories.tsx +20 -0
- package/src/components/button/Button.tsx +66 -0
- package/src/components/button/ConfirmedButton.tsx +66 -0
- package/src/components/button/classes.tsx +56 -0
- package/src/components/button/index.ts +3 -0
- package/src/components/button.ts +1 -0
- package/src/components/camera/Camera.stories.tsx +40 -0
- package/src/components/camera/Camera.tsx +215 -0
- package/src/components/camera/index.ts +1 -0
- package/src/components/camera.ts +1 -0
- package/src/components/card/Card.stories.tsx +41 -0
- package/src/components/card/Card.tsx +68 -0
- package/src/components/card/index.ts +1 -0
- package/src/components/card.ts +1 -0
- package/src/components/checkbox/Checkbox.tsx +46 -0
- package/src/components/checkbox/index.ts +1 -0
- package/src/components/checkbox.ts +1 -0
- package/src/components/chip/Chip.tsx +29 -0
- package/src/components/chip/index.ts +1 -0
- package/src/components/chip.ts +1 -0
- package/src/components/collapsible/Collapsible.tsx +48 -0
- package/src/components/collapsible/index.ts +1 -0
- package/src/components/collapsible.ts +1 -0
- package/src/components/colorPicker/ColorPicker.tsx +82 -0
- package/src/components/colorPicker/index.ts +1 -0
- package/src/components/colorPicker.ts +1 -0
- package/src/components/contextMenu/contextMenu.tsx +43 -0
- package/src/components/contextMenu.ts +1 -0
- package/src/components/dialog/Dialog.stories.tsx +38 -0
- package/src/components/dialog/Dialog.tsx +267 -0
- package/src/components/dialog/index.ts +1 -0
- package/src/components/dialog.ts +1 -0
- package/src/components/divider/Divider.tsx +26 -0
- package/src/components/divider/index.ts +1 -0
- package/src/components/divider.ts +1 -0
- package/src/components/dropdownMenu/DropdownMenu.stories.tsx +47 -0
- package/src/components/dropdownMenu/DropdownMenu.tsx +89 -0
- package/src/components/dropdownMenu/index.ts +1 -0
- package/src/components/dropdownMenu.ts +1 -0
- package/src/components/errorBoundary/ErrorBoundary.tsx +23 -0
- package/src/components/errorBoundary/index.ts +1 -0
- package/src/components/errorBoundary.ts +1 -0
- package/src/components/forms/Form.tsx +9 -0
- package/src/components/forms/FormikForm.tsx +41 -0
- package/src/components/forms/SubmitButton.tsx +15 -0
- package/src/components/forms/TextField.tsx +112 -0
- package/src/components/forms/index.tsx +4 -0
- package/src/components/forms.ts +1 -0
- package/src/components/icon/Icon.tsx +28 -0
- package/src/components/icon/generated/IconSpritesheet.tsx +442 -0
- package/src/components/icon/generated/iconNames.ts +44 -0
- package/src/components/icon/index.ts +3 -0
- package/src/components/icon.ts +1 -0
- package/src/components/imageUploader/ImageUploader.stories.tsx +39 -0
- package/src/components/imageUploader/ImageUploader.tsx +203 -0
- package/src/components/imageUploader/UploadIcon.tsx +23 -0
- package/src/components/imageUploader/index.ts +1 -0
- package/src/components/imageUploader.ts +1 -0
- package/src/components/infiniteLoadTrigger/InfiniteLoadTrigger.tsx +38 -0
- package/src/components/infiniteLoadTrigger.ts +1 -0
- package/src/components/input/Input.stories.tsx +17 -0
- package/src/components/input/Input.tsx +32 -0
- package/src/components/input/index.ts +1 -0
- package/src/components/input.ts +1 -0
- package/src/components/layouts/PageContent.tsx +51 -0
- package/src/components/layouts/PageFixedArea.tsx +17 -0
- package/src/components/layouts/PageNav.tsx +23 -0
- package/src/components/layouts/PageNowPlaying.tsx +24 -0
- package/src/components/layouts/PageRoot.tsx +29 -0
- package/src/components/layouts/PageSection.tsx +23 -0
- package/src/components/layouts/index.tsx +6 -0
- package/src/components/layouts.ts +1 -0
- package/src/components/liveUpdateTextField/LiveUpdateTextField.tsx +132 -0
- package/src/components/liveUpdateTextField/index.ts +1 -0
- package/src/components/liveUpdateTextField.ts +1 -0
- package/src/components/navBar/NavBar.tsx +59 -0
- package/src/components/navBar/index.ts +1 -0
- package/src/components/navBar.ts +1 -0
- package/src/components/note/Note.tsx +21 -0
- package/src/components/note/index.ts +1 -0
- package/src/components/note.ts +1 -0
- package/src/components/numberStepper/NumberStepper.stories.tsx +21 -0
- package/src/components/numberStepper/NumberStepper.tsx +74 -0
- package/src/components/numberStepper/index.ts +1 -0
- package/src/components/numberStepper.ts +1 -0
- package/src/components/particles/ParticleContext.tsx +11 -0
- package/src/components/particles/ParticleLayer.stories.tsx +46 -0
- package/src/components/particles/ParticleLayer.tsx +28 -0
- package/src/components/particles/index.ts +7 -0
- package/src/components/particles/particlesState.ts +502 -0
- package/src/components/particles.ts +1 -0
- package/src/components/peek/Peek.tsx +74 -0
- package/src/components/peek/index.ts +1 -0
- package/src/components/peek.ts +1 -0
- package/src/components/popover/Popover.tsx +84 -0
- package/src/components/popover/index.ts +1 -0
- package/src/components/popover.ts +1 -0
- package/src/components/relativeTime/RelativeTime.tsx +43 -0
- package/src/components/relativeTime/index.ts +1 -0
- package/src/components/relativeTime.ts +1 -0
- package/src/components/richEditor/EditorContent.tsx +4 -0
- package/src/components/richEditor/RichEditor.tsx +38 -0
- package/src/components/richEditor/index.ts +1 -0
- package/src/components/richEditor.ts +1 -0
- package/src/components/select/Select.tsx +247 -0
- package/src/components/select/index.ts +1 -0
- package/src/components/select.ts +1 -0
- package/src/components/skeletons/skeletons.tsx +27 -0
- package/src/components/skeletons.ts +1 -0
- package/src/components/spinner/Spinner.tsx +59 -0
- package/src/components/spinner/index.ts +1 -0
- package/src/components/spinner.ts +1 -0
- package/src/components/switch/Switch.tsx +23 -0
- package/src/components/switch/index.ts +1 -0
- package/src/components/switch.ts +1 -0
- package/src/components/tabs/tabs.tsx +18 -0
- package/src/components/tabs.ts +1 -0
- package/src/components/textArea/TextArea.stories.tsx +21 -0
- package/src/components/textArea/TextArea.tsx +58 -0
- package/src/components/textArea/index.ts +1 -0
- package/src/components/textArea.ts +1 -0
- package/src/components/toggleGroup/toggleGroup.tsx +11 -0
- package/src/components/toggleGroup.ts +1 -0
- package/src/components/tooltip/Tooltip.tsx +56 -0
- package/src/components/tooltip/index.ts +1 -0
- package/src/components/tooltip.ts +1 -0
- package/src/components/typography/index.ts +1 -0
- package/src/components/typography/typography.tsx +18 -0
- package/src/components/typography.ts +1 -0
- package/src/hooks/index.ts +7 -0
- package/src/hooks/useMergedRef.ts +14 -0
- package/src/hooks/useOnUnmount.ts +20 -0
- package/src/hooks/useSize.ts +164 -0
- package/src/hooks/useStableCallback.ts +11 -0
- package/src/hooks/useToggle.tsx +9 -0
- package/src/hooks/useVisualViewportOffset.ts +35 -0
- package/src/hooks/withClassName.tsx +21 -0
- package/src/hooks.ts +1 -0
- package/src/uno.preset.ts +767 -0
package/package.json
CHANGED
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { ReactNode, Suspense } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
|
|
4
|
+
export interface ActionBarProps {
|
|
5
|
+
children: ReactNode;
|
|
6
|
+
className?: string;
|
|
7
|
+
wrap?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function ActionBar({
|
|
11
|
+
children,
|
|
12
|
+
className,
|
|
13
|
+
wrap,
|
|
14
|
+
...rest
|
|
15
|
+
}: ActionBarProps) {
|
|
16
|
+
return (
|
|
17
|
+
<Suspense fallback={null}>
|
|
18
|
+
<div
|
|
19
|
+
className={classNames(
|
|
20
|
+
'flex flex-row items-center justify-start w-full overflow-hidden relative h-[max-content] transition-[height] springy',
|
|
21
|
+
'[&:empty]:height-0',
|
|
22
|
+
'after:(content-[""] absolute right-0 top-0 bottom-0 w-50px z-1 bg-gradient-to-l from-wash to-transparent] pointer-events-none)',
|
|
23
|
+
className,
|
|
24
|
+
)}
|
|
25
|
+
{...rest}
|
|
26
|
+
>
|
|
27
|
+
<div
|
|
28
|
+
className={classNames(
|
|
29
|
+
'flex flex-row items-center justify-start w-full overflow-y-hidden overflow-x-auto pr-80px relative h-full [&::-webkit-scrollbar]:display-none',
|
|
30
|
+
wrap && 'flex-wrap',
|
|
31
|
+
)}
|
|
32
|
+
>
|
|
33
|
+
{children}
|
|
34
|
+
</div>
|
|
35
|
+
</div>
|
|
36
|
+
</Suspense>
|
|
37
|
+
);
|
|
38
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import classNames from 'classnames';
|
|
4
|
+
import { forwardRef, ReactNode, useEffect, useRef, useState } from 'react';
|
|
5
|
+
import { Button, ButtonProps } from '../button/Button.js';
|
|
6
|
+
import { CollapsibleContent, CollapsibleRoot } from '../collapsible.js';
|
|
7
|
+
|
|
8
|
+
export interface ActionButtonProps extends ButtonProps {
|
|
9
|
+
icon?: ReactNode;
|
|
10
|
+
visible?: boolean;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export const ActionButton = forwardRef<HTMLButtonElement, ActionButtonProps>(
|
|
14
|
+
function ActionButton(
|
|
15
|
+
{ icon, children, className, visible = true, ...rest },
|
|
16
|
+
ref,
|
|
17
|
+
) {
|
|
18
|
+
// this rather convoluted logic is meant to do:
|
|
19
|
+
// - when button goes invisible, wait for collapse and then
|
|
20
|
+
// stop rendering
|
|
21
|
+
// - when button goes visible, render immediately and
|
|
22
|
+
// set collapsible open next frame.
|
|
23
|
+
const [render, setRender] = useState(visible);
|
|
24
|
+
useEffect(() => {
|
|
25
|
+
if (!visible) {
|
|
26
|
+
const timeout = setTimeout(() => {
|
|
27
|
+
setRender(visible);
|
|
28
|
+
}, 300);
|
|
29
|
+
return () => clearTimeout(timeout);
|
|
30
|
+
} else {
|
|
31
|
+
setRender(visible);
|
|
32
|
+
}
|
|
33
|
+
}, [visible]);
|
|
34
|
+
|
|
35
|
+
if (!render && !visible) {
|
|
36
|
+
return null;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
return (
|
|
40
|
+
<CollapsibleRoot open={!visible ? false : render}>
|
|
41
|
+
<CollapsibleContent data-horizontal>
|
|
42
|
+
<Button
|
|
43
|
+
ref={ref}
|
|
44
|
+
size="small"
|
|
45
|
+
className={classNames(
|
|
46
|
+
'important:(border-gray7 font-normal whitespace-nowrap m-2 flex flex-row gap-2 items-center h-30px rounded-15px mx-1)',
|
|
47
|
+
'hover:bg-gray2',
|
|
48
|
+
className,
|
|
49
|
+
)}
|
|
50
|
+
{...rest}
|
|
51
|
+
>
|
|
52
|
+
{icon}
|
|
53
|
+
{children}
|
|
54
|
+
</Button>
|
|
55
|
+
</CollapsibleContent>
|
|
56
|
+
</CollapsibleRoot>
|
|
57
|
+
);
|
|
58
|
+
},
|
|
59
|
+
);
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './actions/index.js';
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { CSSProperties } from 'react';
|
|
3
|
+
import { Icon } from '../icon.js';
|
|
4
|
+
|
|
5
|
+
export interface AvatarProps {
|
|
6
|
+
className?: string;
|
|
7
|
+
popIn?: boolean;
|
|
8
|
+
style?: CSSProperties;
|
|
9
|
+
imageSrc?: string;
|
|
10
|
+
name?: string;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
export function Avatar({
|
|
14
|
+
className,
|
|
15
|
+
popIn = true,
|
|
16
|
+
imageSrc,
|
|
17
|
+
name,
|
|
18
|
+
...rest
|
|
19
|
+
}: AvatarProps) {
|
|
20
|
+
const empty = !name && !imageSrc;
|
|
21
|
+
return (
|
|
22
|
+
<div
|
|
23
|
+
data-pop={popIn}
|
|
24
|
+
className={classNames(
|
|
25
|
+
'layer-components:(flex items-center justify-center rounded-full border-default p-2px overflow-hidden w-24px h-24px select-none relative bg-white flex-shrink-0)',
|
|
26
|
+
popIn &&
|
|
27
|
+
'layer-variants:(animate-pop-in-from-half animate-ease-springy animate-duration-200)',
|
|
28
|
+
empty && 'layer-components(border-dashed bg-gray2)',
|
|
29
|
+
className,
|
|
30
|
+
)}
|
|
31
|
+
{...rest}
|
|
32
|
+
>
|
|
33
|
+
{!empty && <AvatarContent name={name} imageSrc={imageSrc} />}
|
|
34
|
+
{empty && <Icon name="profile" />}
|
|
35
|
+
</div>
|
|
36
|
+
);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function AvatarContent({
|
|
40
|
+
name,
|
|
41
|
+
imageSrc,
|
|
42
|
+
}: {
|
|
43
|
+
name?: string;
|
|
44
|
+
imageSrc?: string;
|
|
45
|
+
}) {
|
|
46
|
+
if (imageSrc) {
|
|
47
|
+
return (
|
|
48
|
+
<img
|
|
49
|
+
className="w-full h-full object-cover rounded-full"
|
|
50
|
+
referrerPolicy="no-referrer"
|
|
51
|
+
crossOrigin="anonymous"
|
|
52
|
+
src={imageSrc}
|
|
53
|
+
alt={`${name ?? 'user'}'s profile picture`}
|
|
54
|
+
/>
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
return (
|
|
58
|
+
<div className="color-black items-center justify-center flex text-sm font-bold rounded-full">
|
|
59
|
+
{name?.charAt(0) || '?'}
|
|
60
|
+
</div>
|
|
61
|
+
);
|
|
62
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { ReactNode, createContext, useContext } from 'react';
|
|
2
|
+
import classNames from 'classnames';
|
|
3
|
+
import { Avatar, AvatarProps } from './Avatar.js';
|
|
4
|
+
|
|
5
|
+
const AvatarListContext = createContext<{ size: number }>({
|
|
6
|
+
size: 24,
|
|
7
|
+
});
|
|
8
|
+
|
|
9
|
+
export function AvatarList({
|
|
10
|
+
children,
|
|
11
|
+
count,
|
|
12
|
+
size = 24,
|
|
13
|
+
}: {
|
|
14
|
+
children: ReactNode;
|
|
15
|
+
count: number;
|
|
16
|
+
size?: number;
|
|
17
|
+
}) {
|
|
18
|
+
const width = count > 0 ? size + (count - 1) * ((size * 2) / 3) : 0;
|
|
19
|
+
|
|
20
|
+
return (
|
|
21
|
+
<AvatarListContext.Provider value={{ size }}>
|
|
22
|
+
<div
|
|
23
|
+
className="relative flex-basis-auto"
|
|
24
|
+
style={{ width, minWidth: width, height: size }}
|
|
25
|
+
>
|
|
26
|
+
{children}
|
|
27
|
+
</div>
|
|
28
|
+
</AvatarListContext.Provider>
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
export function AvatarListItemRoot({
|
|
33
|
+
index,
|
|
34
|
+
children,
|
|
35
|
+
className,
|
|
36
|
+
}: {
|
|
37
|
+
index: number;
|
|
38
|
+
children: ReactNode;
|
|
39
|
+
className?: string;
|
|
40
|
+
}) {
|
|
41
|
+
const { size } = useContext(AvatarListContext);
|
|
42
|
+
return (
|
|
43
|
+
<div
|
|
44
|
+
className={classNames('absolute', className)}
|
|
45
|
+
style={{
|
|
46
|
+
left: index === 0 ? 0 : index * ((size * 2) / 3),
|
|
47
|
+
zIndex: index,
|
|
48
|
+
top: 0,
|
|
49
|
+
}}
|
|
50
|
+
>
|
|
51
|
+
{children}
|
|
52
|
+
</div>
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export function AvatarListItem({
|
|
57
|
+
index,
|
|
58
|
+
className,
|
|
59
|
+
...rest
|
|
60
|
+
}: {
|
|
61
|
+
index: number;
|
|
62
|
+
popIn?: boolean;
|
|
63
|
+
className?: string;
|
|
64
|
+
} & AvatarProps) {
|
|
65
|
+
const { size } = useContext(AvatarListContext);
|
|
66
|
+
return (
|
|
67
|
+
<AvatarListItemRoot index={index} className={className}>
|
|
68
|
+
<Avatar style={{ width: size, height: size }} {...rest} />
|
|
69
|
+
</AvatarListItemRoot>
|
|
70
|
+
);
|
|
71
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './avatar/index.js';
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import { Button } from './Button.js';
|
|
3
|
+
|
|
4
|
+
const meta = {
|
|
5
|
+
title: 'Button',
|
|
6
|
+
component: Button,
|
|
7
|
+
argTypes: {},
|
|
8
|
+
parameters: {
|
|
9
|
+
controls: { expanded: true },
|
|
10
|
+
},
|
|
11
|
+
args: {
|
|
12
|
+
children: 'Button',
|
|
13
|
+
},
|
|
14
|
+
} satisfies Meta<typeof Button>;
|
|
15
|
+
|
|
16
|
+
export default meta;
|
|
17
|
+
|
|
18
|
+
type Story = StoryObj<typeof Button>;
|
|
19
|
+
|
|
20
|
+
export const Default: Story = {};
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import { forwardRef, ButtonHTMLAttributes } from 'react';
|
|
3
|
+
import { Spinner } from '../spinner.js';
|
|
4
|
+
import { getButtonClassName } from './classes.js';
|
|
5
|
+
import { Slot } from '@radix-ui/react-slot';
|
|
6
|
+
|
|
7
|
+
export interface ButtonProps extends ButtonHTMLAttributes<HTMLButtonElement> {
|
|
8
|
+
color?:
|
|
9
|
+
| 'primary'
|
|
10
|
+
| 'default'
|
|
11
|
+
| 'ghost'
|
|
12
|
+
| 'destructive'
|
|
13
|
+
| 'ghostDestructive'
|
|
14
|
+
| 'accent'
|
|
15
|
+
| 'contrast';
|
|
16
|
+
size?: 'default' | 'small' | 'icon';
|
|
17
|
+
toggled?: boolean;
|
|
18
|
+
align?: 'start' | 'stretch' | 'end';
|
|
19
|
+
visuallyDisabled?: boolean;
|
|
20
|
+
loading?: boolean;
|
|
21
|
+
asChild?: boolean;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
25
|
+
function Button(
|
|
26
|
+
{
|
|
27
|
+
className,
|
|
28
|
+
color,
|
|
29
|
+
size,
|
|
30
|
+
toggled,
|
|
31
|
+
align,
|
|
32
|
+
visuallyDisabled,
|
|
33
|
+
loading,
|
|
34
|
+
children,
|
|
35
|
+
disabled,
|
|
36
|
+
asChild,
|
|
37
|
+
...props
|
|
38
|
+
},
|
|
39
|
+
ref,
|
|
40
|
+
) {
|
|
41
|
+
const Comp = asChild ? Slot : 'button';
|
|
42
|
+
const buttonProps = {
|
|
43
|
+
ref: ref,
|
|
44
|
+
...props,
|
|
45
|
+
disabled: disabled || loading,
|
|
46
|
+
'data-disabled': visuallyDisabled,
|
|
47
|
+
tabIndex: visuallyDisabled ? -1 : undefined,
|
|
48
|
+
className: classNames(
|
|
49
|
+
getButtonClassName({ color, size, toggled, align }),
|
|
50
|
+
className,
|
|
51
|
+
),
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (asChild) {
|
|
55
|
+
// avoid rendering loading spinner with asChild
|
|
56
|
+
return <Comp {...buttonProps}>{children}</Comp>;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
return (
|
|
60
|
+
<Comp {...buttonProps}>
|
|
61
|
+
{loading && <Spinner size={16} className="inline-block w-1em h-1em" />}
|
|
62
|
+
{children}
|
|
63
|
+
</Comp>
|
|
64
|
+
);
|
|
65
|
+
},
|
|
66
|
+
);
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
import { useCallback, useState } from 'react';
|
|
2
|
+
import {
|
|
3
|
+
Dialog,
|
|
4
|
+
DialogActions,
|
|
5
|
+
DialogClose,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogTitle,
|
|
8
|
+
DialogTrigger,
|
|
9
|
+
} from '../dialog.js';
|
|
10
|
+
import { P } from '../typography.js';
|
|
11
|
+
import { Button, ButtonProps } from './Button.js';
|
|
12
|
+
|
|
13
|
+
export interface ConfirmedButtonProps extends Omit<ButtonProps, 'onClick'> {
|
|
14
|
+
confirmText: string;
|
|
15
|
+
confirmTitle?: string;
|
|
16
|
+
confirmAction?: string;
|
|
17
|
+
cancelAction?: string;
|
|
18
|
+
onConfirm: () => void | Promise<any>;
|
|
19
|
+
skip?: boolean;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export function ConfirmedButton({
|
|
23
|
+
confirmText,
|
|
24
|
+
confirmTitle = 'Are you sure?',
|
|
25
|
+
confirmAction = 'OK',
|
|
26
|
+
cancelAction = 'Nevermind',
|
|
27
|
+
onConfirm,
|
|
28
|
+
skip,
|
|
29
|
+
...rest
|
|
30
|
+
}: ConfirmedButtonProps) {
|
|
31
|
+
const [open, setOpen] = useState(false);
|
|
32
|
+
const [loading, setLoading] = useState(false);
|
|
33
|
+
const confirm = useCallback(async () => {
|
|
34
|
+
setLoading(true);
|
|
35
|
+
try {
|
|
36
|
+
await onConfirm();
|
|
37
|
+
setOpen(false);
|
|
38
|
+
} finally {
|
|
39
|
+
setLoading(false);
|
|
40
|
+
}
|
|
41
|
+
}, [onConfirm]);
|
|
42
|
+
|
|
43
|
+
if (skip) {
|
|
44
|
+
return <Button {...rest} />;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return (
|
|
48
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
49
|
+
<DialogTrigger asChild>
|
|
50
|
+
<Button {...rest} />
|
|
51
|
+
</DialogTrigger>
|
|
52
|
+
<DialogContent>
|
|
53
|
+
<DialogTitle>{confirmTitle}</DialogTitle>
|
|
54
|
+
<P>{confirmText}</P>
|
|
55
|
+
<DialogActions>
|
|
56
|
+
<DialogClose asChild>
|
|
57
|
+
<Button>{cancelAction}</Button>
|
|
58
|
+
</DialogClose>
|
|
59
|
+
<Button loading={loading} onClick={confirm} color="primary">
|
|
60
|
+
{confirmAction}
|
|
61
|
+
</Button>
|
|
62
|
+
</DialogActions>
|
|
63
|
+
</DialogContent>
|
|
64
|
+
</Dialog>
|
|
65
|
+
);
|
|
66
|
+
}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import classNames from 'classnames';
|
|
2
|
+
import type { ButtonProps } from './Button.jsx';
|
|
3
|
+
|
|
4
|
+
export function getButtonClassName({
|
|
5
|
+
color,
|
|
6
|
+
size,
|
|
7
|
+
toggled,
|
|
8
|
+
align,
|
|
9
|
+
}: {
|
|
10
|
+
color?: ButtonProps['color'];
|
|
11
|
+
size?: ButtonProps['size'];
|
|
12
|
+
toggled?: boolean;
|
|
13
|
+
align?: ButtonProps['align'];
|
|
14
|
+
}) {
|
|
15
|
+
return classNames(
|
|
16
|
+
'layer-components:(px-4 py-2 bg-[var(--bg)] [--webkit-tap-highlight-color:transparent] [line-height:1] text-size-md font-sans border border-solid border-transparent rounded-full cursor-pointer font-bold flex flex-row gap-1 items-center relative overflow-visible select-none all:transition duration-200 shadow-none whitespace-nowrap)',
|
|
17
|
+
'layer-components:hover:(bg-[var(--hover)] shadow-[0_0_0_6px_var(--hover)])',
|
|
18
|
+
'layer-components:focus:outline-off',
|
|
19
|
+
'layer-components:focus-visible:(outline-off shadow-[0_0_0_6px_var(--focus,var(--hover))])',
|
|
20
|
+
'layer-components:active:(shadow-[0_0_0_6px_var(--active)] bg-[var(--active)])',
|
|
21
|
+
'important:disabled:(opacity-50 cursor-default bg-[(--var-bg)] shadow-none)',
|
|
22
|
+
'important:[&[data-disabled=true]]:(opacity-50 cursor-default bg-[(--var-bg)] shadow-none)',
|
|
23
|
+
colors[color ?? 'default'],
|
|
24
|
+
`btn-color-${color ?? 'default'}`,
|
|
25
|
+
sizes[size ?? 'default'],
|
|
26
|
+
`size-${size ?? 'default'}`,
|
|
27
|
+
toggled && toggledClass,
|
|
28
|
+
align && aligns[align],
|
|
29
|
+
// compound variants
|
|
30
|
+
color === 'ghost' && toggled && 'layer-variants:bg-primary-wash',
|
|
31
|
+
);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const colors = {
|
|
35
|
+
primary: `layer-variants:[&.btn-color-primary]:([--bg:var(--color-primary-light)] [--hover:var(--color-primary)] [--focus:var(--color-primary)] [--active:var(--color-primary)] color-black border-black focus-visible:([--bg:var(--color-primary)]))`,
|
|
36
|
+
accent: `layer-variants:[&.btn-color-accent]:([--bg:var(--color-accent-wash)] [--hover:var(--color-accent-light)] [--focus:var(--color-accent-light)] [--active:var(--color-accent-light)] color-black border-black focus-visible:([--bg:var(--color-accent-light)]))`,
|
|
37
|
+
default: `layer-variants:[&.btn-color-default]:([--bg:var(--color-white)] [--hover:var(--color-gray-2)] [--focus:var(--color-gray-7)] [--active:var(--color-gray-3)] color-black border-black)`,
|
|
38
|
+
ghost: `layer-variants:[&.btn-color-ghost]:([--bg:transparent] [--hover:var(--color-gray-blend)] [--focus:var(--color-gray-7)] [--active:var(--color-gray-dark-blend)] color-dark-blend hover:([--bg:var(--color-gray-blend)]) focus-visible:([--bg:var(--color-gray-blend)] [--hover:var(--color-gray-7)]))`,
|
|
39
|
+
destructive: `layer-variants:[&.btn-color-destructive]:([--bg:var(--color-attention-light)] [--hover:var(--color-attention-light)] [--focus:var(--color-attention-light)] [--active:var(--color-attention-light)] border-black color-black hover:([--bg:var(--colors-attention)]))`,
|
|
40
|
+
ghostDestructive: `layer-variants:[&.btn-color-ghostDestructive]:([--bg:transparent] [--hover:var(--color-attention-light)] [--focus:var(--color-attention-light)] [--active:var(-color-attention-light)] color-attention-dark)`,
|
|
41
|
+
contrast: `layer-variants:[&.btn-color-contrast]:([--bg:var(--color-black)] [--hover:var(--color-gray-7)] [--focus:var(--color-gray-7)] [--active:var(--color-gray-6)] color-white border-black)`,
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
const sizes = {
|
|
45
|
+
default: '',
|
|
46
|
+
small: 'layer-variants:[&.size-small]:(px-4 py-1 text-sm rounded-full)',
|
|
47
|
+
icon: 'layer-variants:[&.size-icon]:(p-2 text-sm rounded-full) layer-variants:focus-visible:shadow-[0_0_0_2px_var(--focus,var(--hover))]',
|
|
48
|
+
};
|
|
49
|
+
|
|
50
|
+
const toggledClass = 'hover:(filter-brightness-[1.1])';
|
|
51
|
+
|
|
52
|
+
const aligns = {
|
|
53
|
+
start: 'self-start',
|
|
54
|
+
stretch: 'self-stretch',
|
|
55
|
+
end: 'self-end',
|
|
56
|
+
};
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export * from './button/index.js';
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { Meta, StoryObj } from '@storybook/react';
|
|
2
|
+
import {
|
|
3
|
+
CameraDeviceSelector,
|
|
4
|
+
CameraRoot,
|
|
5
|
+
CameraShutterButton,
|
|
6
|
+
} from './Camera.js';
|
|
7
|
+
import { useEffect, useState } from 'react';
|
|
8
|
+
|
|
9
|
+
const meta = {
|
|
10
|
+
title: 'Camera',
|
|
11
|
+
argTypes: {},
|
|
12
|
+
parameters: {
|
|
13
|
+
controls: { expanded: true },
|
|
14
|
+
},
|
|
15
|
+
args: {},
|
|
16
|
+
} satisfies Meta;
|
|
17
|
+
|
|
18
|
+
export default meta;
|
|
19
|
+
|
|
20
|
+
type Story = StoryObj;
|
|
21
|
+
|
|
22
|
+
export const Default: Story = {
|
|
23
|
+
render() {
|
|
24
|
+
return <CameraDemo />;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
function CameraDemo() {
|
|
29
|
+
const [latest, setLatest] = useState<string | undefined>();
|
|
30
|
+
|
|
31
|
+
return (
|
|
32
|
+
<div>
|
|
33
|
+
<CameraRoot onCapture={setLatest} className="w-64 h-64">
|
|
34
|
+
<CameraShutterButton />
|
|
35
|
+
<CameraDeviceSelector />
|
|
36
|
+
</CameraRoot>
|
|
37
|
+
{latest && <img src={latest} className="w-full" />}
|
|
38
|
+
</div>
|
|
39
|
+
);
|
|
40
|
+
}
|