mirror of
https://github.com/morten-olsen/refocus.dev.git
synced 2026-02-08 00:46:25 +01:00
init
This commit is contained in:
2
packages/ui/.gitignore
vendored
Normal file
2
packages/ui/.gitignore
vendored
Normal file
@@ -0,0 +1,2 @@
|
||||
/node_modules/
|
||||
/dist/
|
||||
17
packages/ui/.storybook/main.ts
Normal file
17
packages/ui/.storybook/main.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import type { StorybookConfig } from '@storybook/react-vite';
|
||||
const config: StorybookConfig = {
|
||||
stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'],
|
||||
addons: [
|
||||
'@storybook/addon-links',
|
||||
'@storybook/addon-essentials',
|
||||
'@storybook/addon-interactions',
|
||||
],
|
||||
framework: {
|
||||
name: '@storybook/react-vite',
|
||||
options: {},
|
||||
},
|
||||
docs: {
|
||||
autodocs: 'tag',
|
||||
},
|
||||
};
|
||||
export default config;
|
||||
24
packages/ui/.storybook/preview.tsx
Normal file
24
packages/ui/.storybook/preview.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import React from 'react';
|
||||
import type { Preview } from '@storybook/react';
|
||||
import { UIProvider } from '../src/theme/provider';
|
||||
|
||||
const preview: Preview = {
|
||||
decorators: [
|
||||
(Story) => (
|
||||
<UIProvider>
|
||||
<Story />
|
||||
</UIProvider>
|
||||
),
|
||||
],
|
||||
parameters: {
|
||||
actions: { argTypesRegex: '^on[A-Z].*' },
|
||||
controls: {
|
||||
matchers: {
|
||||
color: /(background|color)$/i,
|
||||
date: /Date$/,
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
export default preview;
|
||||
53
packages/ui/package.json
Normal file
53
packages/ui/package.json
Normal file
@@ -0,0 +1,53 @@
|
||||
{
|
||||
"devDependencies": {
|
||||
"@refocus/config": "workspace:^",
|
||||
"@storybook/addon-essentials": "^7.0.20",
|
||||
"@storybook/addon-interactions": "^7.0.20",
|
||||
"@storybook/addon-links": "^7.0.20",
|
||||
"@storybook/blocks": "^7.0.20",
|
||||
"@storybook/react": "^7.0.20",
|
||||
"@storybook/react-vite": "^7.0.20",
|
||||
"@storybook/testing-library": "^0.0.14-next.2",
|
||||
"@types/react": "^18.0.37",
|
||||
"@types/styled-components": "^5.1.26",
|
||||
"prop-types": "^15.8.1",
|
||||
"react": "^18.2.0",
|
||||
"react-dom": "^18.2.0",
|
||||
"storybook": "^7.0.20",
|
||||
"typescript": "^5.0.4"
|
||||
},
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"default": "./dist/esm/index.js",
|
||||
"types": "./dist/esm/types/index.d.ts"
|
||||
},
|
||||
"require": {
|
||||
"default": "./dist/cjs/index.js",
|
||||
"types": "./dist/cjs/types/index.d.ts"
|
||||
}
|
||||
}
|
||||
},
|
||||
"files": [
|
||||
"dist/**/*"
|
||||
],
|
||||
"main": "./dist/cjs/index.js",
|
||||
"name": "@refocus/ui",
|
||||
"scripts": {
|
||||
"build": "pnpm build:esm && pnpm build:cjs",
|
||||
"build:cjs": "tsc -p tsconfig.json",
|
||||
"build:esm": "tsc -p tsconfig.esm.json",
|
||||
"storybook": "storybook dev -p 6006 --no-open",
|
||||
"build-storybook": "storybook build"
|
||||
},
|
||||
"types": "./dist/cjs/types/index.d.ts",
|
||||
"dependencies": {
|
||||
"@refocus/sdk": "workspace:^",
|
||||
"@radix-ui/react-dialog": "^1.0.4",
|
||||
"@radix-ui/react-dropdown-menu": "^2.0.5",
|
||||
"@radix-ui/react-tabs": "^1.0.4",
|
||||
"react-icons": "^4.9.0",
|
||||
"react-markdown": "^6.0.3",
|
||||
"styled-components": "6.0.0-rc.3"
|
||||
}
|
||||
}
|
||||
84
packages/ui/src/base/avatar/index.tsx
Normal file
84
packages/ui/src/base/avatar/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import styled from 'styled-components';
|
||||
import { useMemo } from 'react';
|
||||
|
||||
type AvatarProps = {
|
||||
url?: string;
|
||||
name?: string;
|
||||
decal?: React.ReactNode;
|
||||
size?: 'sm' | 'md' | 'lg';
|
||||
};
|
||||
|
||||
const sizes = {
|
||||
sm: 28,
|
||||
md: 50,
|
||||
lg: 75,
|
||||
};
|
||||
|
||||
const fontSizes = {
|
||||
sm: 10,
|
||||
md: 24,
|
||||
lg: 32,
|
||||
};
|
||||
|
||||
const Wrapper = styled.div<{
|
||||
size: AvatarProps['size'];
|
||||
}>`
|
||||
position: relative;
|
||||
flex-shrink: 0;
|
||||
${({ size }) => (size ? `width: ${sizes[size]}px;` : '')}
|
||||
${({ size }) => (size ? `height: ${sizes[size]}px;` : '')}
|
||||
`;
|
||||
|
||||
const Image = styled.img`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 3px solid ${({ theme }) => theme.colors.text.base};
|
||||
border-radius: 50%;
|
||||
`;
|
||||
|
||||
const WithoutImage = styled.div<{
|
||||
size: AvatarProps['size'];
|
||||
}>`
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border: 3px solid ${({ theme }) => theme.colors.text.base};
|
||||
border-radius: 50%;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
font-weight: bold;
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.colors.text.base};
|
||||
${({ size }) => (size ? `font-size: ${fontSizes[size]}px;` : '')}
|
||||
`;
|
||||
|
||||
const Decal = styled.div`
|
||||
position: absolute;
|
||||
bottom: 0;
|
||||
right: -5px;
|
||||
min-width: 20px;
|
||||
height: 20px;
|
||||
border-radius: 10px;
|
||||
padding: 0 5px;
|
||||
background-color: ${({ theme }) => theme.colors.text.base};
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
color: ${({ theme }) => theme.colors.bg.base};
|
||||
font-size: 12px;
|
||||
`;
|
||||
|
||||
const Avatar: React.FC<AvatarProps> = ({ url, name, decal, size = 'md' }) => {
|
||||
const initials = useMemo(() => {
|
||||
const [firstName, lastName] = name?.split(' ') || [];
|
||||
return `${firstName?.[0] || ''}${lastName?.[0] || ''}`;
|
||||
}, [name]);
|
||||
return (
|
||||
<Wrapper size={size}>
|
||||
{!url && <WithoutImage size={size}>{initials}</WithoutImage>}
|
||||
{url && <Image src={url} alt={name || ''} />}
|
||||
{decal && <Decal>{decal}</Decal>}
|
||||
</Wrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export { Avatar };
|
||||
30
packages/ui/src/base/button/index.tsx
Normal file
30
packages/ui/src/base/button/index.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { View } from '../view';
|
||||
|
||||
type ButtonProps = {
|
||||
title: React.ReactNode;
|
||||
icon?: React.ReactNode;
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
const ButtonWrapper = styled(View)`
|
||||
background-color: ${({ theme }) => theme.colors.bg.highlight};
|
||||
display: inline-flex;
|
||||
`;
|
||||
|
||||
const Button: React.FC<ButtonProps> = ({ title, onClick, icon }) => {
|
||||
return (
|
||||
<ButtonWrapper
|
||||
$p="sm"
|
||||
as="button"
|
||||
onClick={onClick}
|
||||
$items="center"
|
||||
$gap="sm"
|
||||
>
|
||||
{icon}
|
||||
{title}
|
||||
</ButtonWrapper>
|
||||
);
|
||||
};
|
||||
|
||||
export { Button };
|
||||
24
packages/ui/src/base/card/index.tsx
Normal file
24
packages/ui/src/base/card/index.tsx
Normal file
@@ -0,0 +1,24 @@
|
||||
import styled from 'styled-components';
|
||||
import { View } from '../view';
|
||||
|
||||
const Card = styled(View)`
|
||||
border-radius: ${({ theme }) => `${theme.radii.md}${theme.units.radii}`};
|
||||
|
||||
${({ theme, ...rest }) =>
|
||||
'onClick' in rest &&
|
||||
`
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s ease-in-out, box-shadow 0.2s ease-in-out;
|
||||
&:hover {
|
||||
box-shadow: 0 0 5px 2px ${theme.colors.bg.highlight};
|
||||
background: ${theme.colors.bg.highlight100};
|
||||
}
|
||||
|
||||
&:active {
|
||||
box-shadow: 0 0 3px 2px ${theme.colors.bg.highlight100};
|
||||
background: ${theme.colors.bg.highlight100};
|
||||
}
|
||||
`}
|
||||
`;
|
||||
|
||||
export { Card };
|
||||
30
packages/ui/src/base/dialog/index.stories.tsx
Normal file
30
packages/ui/src/base/dialog/index.stories.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { StoryObj, Meta } from '@storybook/react';
|
||||
import { Dialog } from '.';
|
||||
|
||||
type Story = StoryObj<typeof Dialog>;
|
||||
|
||||
const meta = {
|
||||
title: 'Interface/Dialog',
|
||||
component: Dialog,
|
||||
} satisfies Meta<typeof Dialog>;
|
||||
|
||||
const docs: Story = {
|
||||
render: () => (
|
||||
<Dialog>
|
||||
<Dialog.Trigger asChild>
|
||||
<button>Open Dialog</button>
|
||||
</Dialog.Trigger>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<Dialog.Title>Dialog Title</Dialog.Title>
|
||||
<Dialog.Description>Dialog Description</Dialog.Description>
|
||||
<Dialog.CloseButton />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
export { docs };
|
||||
102
packages/ui/src/base/dialog/index.tsx
Normal file
102
packages/ui/src/base/dialog/index.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import * as DialogPrimitives from '@radix-ui/react-dialog';
|
||||
import React, { forwardRef } from 'react';
|
||||
import { styled } from 'styled-components';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import { styles } from '../../typography';
|
||||
import { View } from '../view';
|
||||
|
||||
const Root = styled(DialogPrimitives.Root)``;
|
||||
|
||||
const Overlay = styled(DialogPrimitives.Overlay)`
|
||||
position: fixed;
|
||||
inset: 0;
|
||||
backdrop-filter: blur(5px);
|
||||
`;
|
||||
|
||||
const Portal = styled(DialogPrimitives.Portal)``;
|
||||
|
||||
const Content = styled(DialogPrimitives.Content)`
|
||||
z-index: 1000;
|
||||
overflow-y: auto;
|
||||
background-color: ${({ theme }) => theme.colors.bg.base100};
|
||||
border-radius: 6px;
|
||||
box-shadow: hsl(206 22% 7% / 35%) 0px 10px 38px -10px,
|
||||
hsl(206 22% 7% / 20%) 0px 10px 20px -15px;
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
left: 50%;
|
||||
transform: translate(-50%, -50%);
|
||||
width: 90vw;
|
||||
max-width: 450px;
|
||||
max-height: 85vh;
|
||||
padding: 25px;
|
||||
`;
|
||||
|
||||
const Title = styled(DialogPrimitives.Title)`
|
||||
margin: 0;
|
||||
font-weight: 500;
|
||||
font-size: 17px;
|
||||
${styles.dialogTitle}
|
||||
`;
|
||||
|
||||
const Description = styled(DialogPrimitives.Description)`
|
||||
margin: 10px 0 20px;
|
||||
color: var(--mauve11);
|
||||
font-size: 15px;
|
||||
line-height: 1.5;
|
||||
`;
|
||||
|
||||
const Trigger = styled(DialogPrimitives.Trigger)``;
|
||||
|
||||
const Close = styled(DialogPrimitives.Close)`
|
||||
all: unset;
|
||||
`;
|
||||
|
||||
const CloseButtonWrapper = styled(DialogPrimitives.Close)`
|
||||
all: unset;
|
||||
font-family: inherit;
|
||||
border-radius: 100%;
|
||||
height: 25px;
|
||||
width: 25px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
position: absolute;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--violet4);
|
||||
}
|
||||
&:focus {
|
||||
box-shadow: 0 0 0 2px var(--violet7);
|
||||
}
|
||||
`;
|
||||
|
||||
const Buttons = styled(View)`
|
||||
display: flex;
|
||||
justify-content: flex-end;
|
||||
`;
|
||||
|
||||
const CloseButton = forwardRef<
|
||||
React.ComponentProps<typeof CloseButtonWrapper>,
|
||||
React.ComponentPropsWithoutRef<typeof CloseButtonWrapper>
|
||||
>((props, forwardedRef) => (
|
||||
<CloseButtonWrapper {...props} ref={forwardedRef as any}>
|
||||
<FiX />
|
||||
</CloseButtonWrapper>
|
||||
));
|
||||
|
||||
const Dialog = Object.assign(Root, {
|
||||
Overlay,
|
||||
Portal,
|
||||
Content,
|
||||
Title,
|
||||
Description,
|
||||
Trigger,
|
||||
CloseButton,
|
||||
Close,
|
||||
Buttons,
|
||||
});
|
||||
|
||||
export { Dialog };
|
||||
43
packages/ui/src/base/dropdown/index.stories.tsx
Normal file
43
packages/ui/src/base/dropdown/index.stories.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { StoryObj, Meta } from '@storybook/react';
|
||||
import { DropdownMenu } from '.';
|
||||
|
||||
type Story = StoryObj<typeof DropdownMenu>;
|
||||
|
||||
const meta = {
|
||||
title: 'Components/DropDown',
|
||||
component: DropdownMenu,
|
||||
} satisfies Meta<typeof DropdownMenu>;
|
||||
|
||||
const docs: Story = {
|
||||
render: () => (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger asChild>
|
||||
<button>Open Dialog</button>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content>
|
||||
<DropdownMenu.Item>
|
||||
Item 1<DropdownMenu.RightSlot>Foo</DropdownMenu.RightSlot>
|
||||
</DropdownMenu.Item>
|
||||
<DropdownMenu.Separator />
|
||||
<DropdownMenu.Sub>
|
||||
<DropdownMenu.SubTrigger>
|
||||
Item 2<DropdownMenu.RightSlot>⌘+A</DropdownMenu.RightSlot>
|
||||
</DropdownMenu.SubTrigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.SubContent sideOffset={2} alignOffset={-5}>
|
||||
<DropdownMenu.Item>Sub Item 1</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Sub Item 2</DropdownMenu.Item>
|
||||
<DropdownMenu.Item>Sub Item 3</DropdownMenu.Item>
|
||||
</DropdownMenu.SubContent>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu.Sub>
|
||||
<DropdownMenu.Arrow />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
),
|
||||
};
|
||||
|
||||
export default meta;
|
||||
export { docs };
|
||||
162
packages/ui/src/base/dropdown/index.tsx
Normal file
162
packages/ui/src/base/dropdown/index.tsx
Normal file
@@ -0,0 +1,162 @@
|
||||
import * as DropdownMenuPrimitives from '@radix-ui/react-dropdown-menu';
|
||||
import { styled, css } from 'styled-components';
|
||||
|
||||
const RightSlot = styled.div`
|
||||
margin-left: auto;
|
||||
padding-left: 20px;
|
||||
color: var(--mauve11);
|
||||
`;
|
||||
|
||||
const content = css`
|
||||
min-width: 220px;
|
||||
background: ${({ theme }) => theme.colors.bg.base100};
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
||||
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||
`;
|
||||
|
||||
const item = css`
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 25px;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
padding-left: 25px;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
|
||||
&[data-state='disabled'] {
|
||||
color: ${({ theme }) => theme.colors.text.disabled};
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&[data-highlighted] {
|
||||
background-color: ${({ theme }) => theme.colors.bg.highlight};
|
||||
color: ${({ theme }) => theme.colors.text.highlight};
|
||||
}
|
||||
|
||||
&[data-highlighted] > ${RightSlot} {
|
||||
color: white;
|
||||
}
|
||||
|
||||
&[data-disabled] ${RightSlot} {
|
||||
color: var(--mauve8);
|
||||
}
|
||||
`;
|
||||
|
||||
const Root = styled(DropdownMenuPrimitives.Root)``;
|
||||
|
||||
const Content = styled(DropdownMenuPrimitives.Content)`
|
||||
${content}
|
||||
`;
|
||||
|
||||
const Trigger = styled(DropdownMenuPrimitives.Trigger)`
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Portal = styled(DropdownMenuPrimitives.Portal)``;
|
||||
|
||||
const Item = styled(DropdownMenuPrimitives.Item)`
|
||||
${item}
|
||||
`;
|
||||
|
||||
const Sub = styled(DropdownMenuPrimitives.Sub)``;
|
||||
|
||||
const SubTrigger = styled(DropdownMenuPrimitives.SubTrigger)`
|
||||
&[data-state='open'] {
|
||||
background-color: ${({ theme }) => theme.colors.bg.highlight100};
|
||||
}
|
||||
|
||||
${item}
|
||||
`;
|
||||
|
||||
const Icon = styled.div`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 25px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const SubContent = styled(DropdownMenuPrimitives.SubContent)`
|
||||
${content}
|
||||
min-width: 220px;
|
||||
border-radius: 6px;
|
||||
padding: 5px;
|
||||
box-shadow: 0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
||||
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||
`;
|
||||
|
||||
const Separator = styled(DropdownMenuPrimitives.Separator)`
|
||||
height: 1px;
|
||||
background-color: ${({ theme }) => theme.colors.bg.highlight100};
|
||||
margin: 5px;
|
||||
`;
|
||||
|
||||
const CheckboxItem = styled(DropdownMenuPrimitives.CheckboxItem)``;
|
||||
|
||||
const ItemIndicator = styled(DropdownMenuPrimitives.ItemIndicator)`
|
||||
position: absolute;
|
||||
left: 0;
|
||||
width: 25px;
|
||||
display: inline-flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
`;
|
||||
|
||||
const Label = styled(DropdownMenuPrimitives.Label)`
|
||||
padding-left: 25px;
|
||||
font-size: 12px;
|
||||
line-height: 25px;
|
||||
color: var(--mauve11);
|
||||
`;
|
||||
|
||||
const RadioGroup = styled(DropdownMenuPrimitives.RadioGroup)``;
|
||||
|
||||
const RadioItem = styled(DropdownMenuPrimitives.RadioItem)`
|
||||
font-size: 13px;
|
||||
line-height: 1;
|
||||
color: var(--violet11);
|
||||
border-radius: 3px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
height: 25px;
|
||||
padding: 0 5px;
|
||||
position: relative;
|
||||
padding-left: 25px;
|
||||
user-select: none;
|
||||
outline: none;
|
||||
`;
|
||||
|
||||
const Arrow = styled(DropdownMenuPrimitives.Arrow)`
|
||||
fill: ${({ theme }) => theme.colors.bg.base100};
|
||||
`;
|
||||
|
||||
const DropdownMenu = Object.assign(Root, {
|
||||
Root,
|
||||
Content,
|
||||
Trigger,
|
||||
Portal,
|
||||
Item,
|
||||
Sub,
|
||||
SubTrigger,
|
||||
SubContent,
|
||||
Separator,
|
||||
CheckboxItem,
|
||||
ItemIndicator,
|
||||
RightSlot,
|
||||
Label,
|
||||
RadioGroup,
|
||||
RadioItem,
|
||||
Arrow,
|
||||
Icon,
|
||||
});
|
||||
|
||||
export { DropdownMenu };
|
||||
9
packages/ui/src/base/index.ts
Normal file
9
packages/ui/src/base/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './card';
|
||||
export * from './panel';
|
||||
export * from './row';
|
||||
export * from './view';
|
||||
export * from './avatar';
|
||||
export * from './dialog';
|
||||
export * from './list';
|
||||
export * from './dropdown';
|
||||
export * from './button';
|
||||
15
packages/ui/src/base/list/index.tsx
Normal file
15
packages/ui/src/base/list/index.tsx
Normal file
@@ -0,0 +1,15 @@
|
||||
import { View } from '../view';
|
||||
|
||||
type ListProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
|
||||
const List = ({ children }: ListProps) => {
|
||||
return (
|
||||
<View $fc $gap="sm" $p="sm">
|
||||
{children}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { List };
|
||||
11
packages/ui/src/base/panel/index.module.scss
Normal file
11
packages/ui/src/base/panel/index.module.scss
Normal file
@@ -0,0 +1,11 @@
|
||||
.root {
|
||||
@apply bg-white rounded-lg shadow-lg;
|
||||
}
|
||||
|
||||
.title {
|
||||
@apply text-2xl font-bold;
|
||||
}
|
||||
|
||||
.content {
|
||||
@apply p-4;
|
||||
}
|
||||
16
packages/ui/src/base/panel/index.tsx
Normal file
16
packages/ui/src/base/panel/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
type PanelProps = {
|
||||
title: string;
|
||||
children: React.ReactNode;
|
||||
className?: string;
|
||||
};
|
||||
|
||||
const Panel: React.FC<PanelProps> = ({ title, children, className }) => {
|
||||
return (
|
||||
<div className={className}>
|
||||
<h1>{title}</h1>
|
||||
<div>{children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Panel };
|
||||
13
packages/ui/src/base/row/index.tsx
Normal file
13
packages/ui/src/base/row/index.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
type RowProps = {
|
||||
title: string;
|
||||
};
|
||||
|
||||
const Row: React.FC<RowProps> = ({ title }) => {
|
||||
return (
|
||||
<div>
|
||||
<h3 className="text-lg font-bold">{title}</h3>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Row };
|
||||
68
packages/ui/src/base/tabs/index.tsx
Normal file
68
packages/ui/src/base/tabs/index.tsx
Normal file
@@ -0,0 +1,68 @@
|
||||
import * as TabsPrimities from '@radix-ui/react-tabs';
|
||||
import { FiX } from 'react-icons/fi';
|
||||
import styled from 'styled-components';
|
||||
|
||||
const Root = styled(TabsPrimities.Root)`
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
height: 100%;
|
||||
`;
|
||||
|
||||
const List = styled(TabsPrimities.List)`
|
||||
flex-shrink: 0;
|
||||
display: flex;
|
||||
border-bottom: 1px solid var(--mauve6);
|
||||
overflow-x: auto;
|
||||
border-bottom: 1px solid ${({ theme }) => theme.colors.bg.base100};
|
||||
`;
|
||||
|
||||
const Trigger = styled(TabsPrimities.Trigger)`
|
||||
font-family: inherit;
|
||||
padding: 0 20px;
|
||||
height: 45px;
|
||||
max-width: 200px;
|
||||
flex: 1;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: space-between;
|
||||
font-size: 15px;
|
||||
line-height: 1;
|
||||
user-select: none;
|
||||
border-right: 1px solid ${({ theme }) => theme.colors.bg.base100};
|
||||
|
||||
&:hover {
|
||||
color: ${({ theme }) => theme.colors.bg.highlight};
|
||||
}
|
||||
|
||||
&[data-state='active'] {
|
||||
color: ${({ theme }) => theme.colors.bg.highlight};
|
||||
box-shadow: inset 0 -1px 0 0 currentColor, 0 2px 0 0 currentColor;
|
||||
}
|
||||
`;
|
||||
|
||||
const Content = styled(TabsPrimities.Content)`
|
||||
flex-grow: 1;
|
||||
outline: none;
|
||||
overflow-y: auto;
|
||||
`;
|
||||
|
||||
const CloseWrapper = styled.div``;
|
||||
|
||||
type CloseProps = {
|
||||
onClick?: () => void;
|
||||
};
|
||||
|
||||
const Close: React.FC<CloseProps> = ({ onClick }) => (
|
||||
<CloseWrapper onClick={onClick}>
|
||||
<FiX />
|
||||
</CloseWrapper>
|
||||
);
|
||||
|
||||
const Tabs = Object.assign(Root, {
|
||||
List,
|
||||
Trigger,
|
||||
Content,
|
||||
Close,
|
||||
});
|
||||
|
||||
export { Tabs };
|
||||
91
packages/ui/src/base/view/index.tsx
Normal file
91
packages/ui/src/base/view/index.tsx
Normal file
@@ -0,0 +1,91 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { Theme } from '../../theme';
|
||||
|
||||
type SizeKey = keyof Theme['space'];
|
||||
type BgKey = keyof Theme['colors']['bg'];
|
||||
type ColorKey = keyof Theme['colors']['text'];
|
||||
|
||||
const getSize = (
|
||||
theme: Theme,
|
||||
sizes: (SizeKey | undefined)[],
|
||||
multi: number = 1,
|
||||
) => {
|
||||
while (sizes.length) {
|
||||
const size = sizes.shift();
|
||||
if (size) {
|
||||
const value = theme.space[size];
|
||||
if (value) {
|
||||
return `${value * multi}${theme.units.space}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
return '0';
|
||||
};
|
||||
|
||||
const View = styled.div<{
|
||||
$br?: boolean;
|
||||
$bg?: BgKey;
|
||||
$m?: SizeKey;
|
||||
$mt?: SizeKey;
|
||||
$mr?: SizeKey;
|
||||
$mb?: SizeKey;
|
||||
$ml?: SizeKey;
|
||||
$mx?: SizeKey;
|
||||
$my?: SizeKey;
|
||||
$mm?: number;
|
||||
$c?: ColorKey;
|
||||
$p?: SizeKey;
|
||||
$pt?: SizeKey;
|
||||
$pr?: SizeKey;
|
||||
$pb?: SizeKey;
|
||||
$pl?: SizeKey;
|
||||
$px?: SizeKey;
|
||||
$py?: SizeKey;
|
||||
$pm?: number;
|
||||
$fr?: boolean;
|
||||
$fc?: boolean;
|
||||
$u?: boolean;
|
||||
$gap?: SizeKey;
|
||||
$flexWrap?: boolean;
|
||||
$f?: number;
|
||||
$items?: 'center' | 'flex-start' | 'flex-end' | 'stretch' | 'baseline';
|
||||
$justify?:
|
||||
| 'center'
|
||||
| 'flex-start'
|
||||
| 'flex-end'
|
||||
| 'space-between'
|
||||
| 'space-around'
|
||||
| 'space-evenly'
|
||||
| 'stretch';
|
||||
}>`
|
||||
${({ $u }) => $u && 'all: unset;'}
|
||||
${({ $br }) => $br && 'border-radius: 5px;'}
|
||||
${({ $gap, theme }) =>
|
||||
$gap && `gap: ${theme.space[$gap]}${theme.units.space};`}
|
||||
${({ $c, theme }) => $c && `color: ${theme.colors.text[$c]};`}
|
||||
${({ $bg, theme }) => $bg && `background-color: ${theme.colors.bg[$bg]};`}
|
||||
margin-top: ${({ theme, $mt, $my, $m, $mm }) =>
|
||||
getSize(theme, [$mt, $my, $m], $mm)};
|
||||
margin-right: ${({ theme, $mr, $mx, $m, $mm }) =>
|
||||
getSize(theme, [$mr, $mx, $m], $mm)};
|
||||
margin-bottom: ${({ theme, $mb, $my, $m, $mm }) =>
|
||||
getSize(theme, [$mb, $my, $m], $mm)};
|
||||
margin-left: ${({ theme, $ml, $mx, $m, $mm }) =>
|
||||
getSize(theme, [$ml, $mx, $m], $mm)};
|
||||
padding-top: ${({ theme, $pt, $py, $p, $pm }) =>
|
||||
getSize(theme, [$pt, $py, $p], $pm)};
|
||||
padding-right: ${({ theme, $pr, $px, $p, $pm }) =>
|
||||
getSize(theme, [$pr, $px, $p], $pm)};
|
||||
padding-bottom: ${({ theme, $pb, $py, $p, $pm }) =>
|
||||
getSize(theme, [$pb, $py, $p], $pm)};
|
||||
padding-left: ${({ theme, $pl, $px, $p, $pm }) =>
|
||||
getSize(theme, [$pl, $px, $p], $pm)};
|
||||
${({ $fr }) => $fr && 'display: flex;'}
|
||||
${({ $fc }) => $fc && 'flex-direction: column; display: flex;'}
|
||||
${({ $f }) => $f && `flex: ${$f};`}
|
||||
${({ $flexWrap }) => $flexWrap && 'flex-wrap: wrap;'}
|
||||
${({ $items }) => $items && `align-items: ${$items};`}
|
||||
${({ $justify }) => $justify && `justify-content: ${$justify};`}
|
||||
`;
|
||||
|
||||
export { View };
|
||||
35
packages/ui/src/chat/compose/index.tsx
Normal file
35
packages/ui/src/chat/compose/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { styled } from 'styled-components';
|
||||
import { View } from '../../base';
|
||||
|
||||
type ComposeProps = {
|
||||
value: string;
|
||||
onValueChange: (value: string) => void;
|
||||
onSend?: () => void;
|
||||
};
|
||||
|
||||
const Input = styled(View)`
|
||||
background: ${({ theme }) => theme.colors.bg.highlight100};
|
||||
padding: ${({ theme }) => `${theme.space.sm}${theme.units.space}`};
|
||||
`;
|
||||
|
||||
const Send = styled.button`
|
||||
all: unset;
|
||||
background: ${({ theme }) => theme.colors.bg.highlight};
|
||||
padding: ${({ theme }) => `${theme.space.sm}${theme.units.space}`};
|
||||
`;
|
||||
|
||||
const Compose: React.FC<ComposeProps> = ({ value, onValueChange, onSend }) => (
|
||||
<View $fr>
|
||||
<Input
|
||||
$f={1}
|
||||
$u
|
||||
placeholder="Type a message..."
|
||||
as="input"
|
||||
value={value}
|
||||
onChange={(e) => onValueChange(e.target.value)}
|
||||
/>
|
||||
{!!onSend && <Send onClick={onSend}>Send</Send>}
|
||||
</View>
|
||||
);
|
||||
|
||||
export { Compose };
|
||||
2
packages/ui/src/chat/index.ts
Normal file
2
packages/ui/src/chat/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './message';
|
||||
export * from './compose';
|
||||
26
packages/ui/src/chat/message/index.stories.tsx
Normal file
26
packages/ui/src/chat/message/index.stories.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
import { Message } from './index';
|
||||
|
||||
const meta = {
|
||||
title: 'Chat/Message',
|
||||
component: Message,
|
||||
} satisfies Meta<typeof Message>;
|
||||
|
||||
type Story = StoryFn<typeof Message>;
|
||||
|
||||
const Normal: Story = {
|
||||
args: {
|
||||
message: {
|
||||
text: 'Hello World',
|
||||
timestamp: new Date('2023-01-01T00:00:00.000Z'),
|
||||
sender: {
|
||||
avatar: 'https://avatars.githubusercontent.com/u/10047061?v=4',
|
||||
name: 'John Doe',
|
||||
},
|
||||
},
|
||||
onPress: () => {},
|
||||
},
|
||||
} as any;
|
||||
|
||||
export { Normal };
|
||||
export default meta;
|
||||
71
packages/ui/src/chat/message/index.tsx
Normal file
71
packages/ui/src/chat/message/index.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import styled from 'styled-components';
|
||||
import { Avatar, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
import { formatRelativeTime } from '../../utils/time';
|
||||
|
||||
type MessageProps = {
|
||||
message: {
|
||||
text: React.ReactNode;
|
||||
timestamp: Date;
|
||||
sender: {
|
||||
avatar?: string;
|
||||
name: string;
|
||||
};
|
||||
};
|
||||
onPress?: () => void;
|
||||
};
|
||||
|
||||
const MessageContainer = styled(View)`
|
||||
background-color: ${({ theme }) => theme.colors.bg.highlight100};
|
||||
margin-bottom: 15px;
|
||||
position: relative;
|
||||
`;
|
||||
|
||||
const ArrowDown = styled.div`
|
||||
position: absolute;
|
||||
bottom: -10px;
|
||||
left: 30px;
|
||||
transform: translateX(-50%);
|
||||
width: 0;
|
||||
height: 0;
|
||||
border-left: 10px solid transparent;
|
||||
border-right: 10px solid transparent;
|
||||
border-top: 10px solid ${({ theme }) => theme.colors.bg.highlight100};
|
||||
`;
|
||||
|
||||
const Message: React.FC<MessageProps> = ({ message, onPress }) => {
|
||||
const [time, setTime] = useState<string>(
|
||||
formatRelativeTime(message.timestamp),
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const interval = setInterval(() => {
|
||||
setTime(formatRelativeTime(message.timestamp));
|
||||
}, 5000);
|
||||
|
||||
return () => clearInterval(interval);
|
||||
});
|
||||
|
||||
return (
|
||||
<View>
|
||||
<MessageContainer $p="md" onClick={onPress}>
|
||||
{message.text}
|
||||
<Typography variant="tiny">{time}</Typography>
|
||||
<ArrowDown />
|
||||
</MessageContainer>
|
||||
<View $fr $gap="sm" $items="center" $px="md">
|
||||
<Avatar
|
||||
size="sm"
|
||||
url={message.sender.avatar}
|
||||
name={message.sender.name}
|
||||
/>
|
||||
<View>
|
||||
<Typography variant="overline">{message.sender.name}</Typography>
|
||||
</View>
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { Message };
|
||||
237
packages/ui/src/github/action/data.json
Normal file
237
packages/ui/src/github/action/data.json
Normal file
@@ -0,0 +1,237 @@
|
||||
{
|
||||
"id": 30433642,
|
||||
"name": "Build",
|
||||
"node_id": "MDEyOldvcmtmbG93IFJ1bjI2OTI4OQ==",
|
||||
"check_suite_id": 42,
|
||||
"check_suite_node_id": "MDEwOkNoZWNrU3VpdGU0Mg==",
|
||||
"head_branch": "main",
|
||||
"head_sha": "acb5820ced9479c074f688cc328bf03f341a511d",
|
||||
"path": ".github/workflows/build.yml@main",
|
||||
"run_number": 562,
|
||||
"event": "push",
|
||||
"display_title": "Update README.md",
|
||||
"status": "queued",
|
||||
"conclusion": null,
|
||||
"workflow_id": 159038,
|
||||
"url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642",
|
||||
"html_url": "https://github.com/octo-org/octo-repo/actions/runs/30433642",
|
||||
"pull_requests": [],
|
||||
"created_at": "2020-01-22T19:33:08Z",
|
||||
"updated_at": "2020-01-22T19:33:08Z",
|
||||
"actor": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"run_attempt": 1,
|
||||
"referenced_workflows": [
|
||||
{
|
||||
"path": "octocat/Hello-World/.github/workflows/deploy.yml@main",
|
||||
"sha": "86e8bc9ecf7d38b1ed2d2cfb8eb87ba9b35b01db",
|
||||
"ref": "refs/heads/main"
|
||||
},
|
||||
{
|
||||
"path": "octo-org/octo-repo/.github/workflows/report.yml@v2",
|
||||
"sha": "79e9790903e1c3373b1a3e3a941d57405478a232",
|
||||
"ref": "refs/tags/v2"
|
||||
},
|
||||
{
|
||||
"path": "octo-org/octo-repo/.github/workflows/secure.yml@1595d4b6de6a9e9751fb270a41019ce507d4099e",
|
||||
"sha": "1595d4b6de6a9e9751fb270a41019ce507d4099e"
|
||||
}
|
||||
],
|
||||
"run_started_at": "2020-01-22T19:33:08Z",
|
||||
"triggering_actor": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"jobs_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/jobs",
|
||||
"logs_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/logs",
|
||||
"check_suite_url": "https://api.github.com/repos/octo-org/octo-repo/check-suites/414944374",
|
||||
"artifacts_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/artifacts",
|
||||
"cancel_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/cancel",
|
||||
"rerun_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/rerun",
|
||||
"previous_attempt_url": "https://api.github.com/repos/octo-org/octo-repo/actions/runs/30433642/attempts/1",
|
||||
"workflow_url": "https://api.github.com/repos/octo-org/octo-repo/actions/workflows/159038",
|
||||
"head_commit": {
|
||||
"id": "acb5820ced9479c074f688cc328bf03f341a511d",
|
||||
"tree_id": "d23f6eedb1e1b9610bbc754ddb5197bfe7271223",
|
||||
"message": "Create linter.yaml",
|
||||
"timestamp": "2020-01-22T19:33:05Z",
|
||||
"author": {
|
||||
"name": "Octo Cat",
|
||||
"email": "octocat@github.com"
|
||||
},
|
||||
"committer": {
|
||||
"name": "GitHub",
|
||||
"email": "noreply@github.com"
|
||||
}
|
||||
},
|
||||
"repository": {
|
||||
"id": 1296269,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
|
||||
"name": "Hello-World",
|
||||
"full_name": "octocat/Hello-World",
|
||||
"owner": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/octocat/Hello-World",
|
||||
"description": "This your first repo!",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World",
|
||||
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
|
||||
"events_url": "https://api.github.com/repos/octocat/Hello-World/events",
|
||||
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
|
||||
"git_url": "git:github.com/octocat/Hello-World.git",
|
||||
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
|
||||
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
|
||||
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
|
||||
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
||||
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
|
||||
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
|
||||
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
|
||||
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
|
||||
"hooks_url": "http://api.github.com/repos/octocat/Hello-World/hooks"
|
||||
},
|
||||
"head_repository": {
|
||||
"id": 217723378,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkyMTc3MjMzNzg=",
|
||||
"name": "octo-repo",
|
||||
"full_name": "octo-org/octo-repo",
|
||||
"private": true,
|
||||
"owner": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"html_url": "https://github.com/octo-org/octo-repo",
|
||||
"description": null,
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/octo-org/octo-repo",
|
||||
"forks_url": "https://api.github.com/repos/octo-org/octo-repo/forks",
|
||||
"keys_url": "https://api.github.com/repos/octo-org/octo-repo/keys{/key_id}",
|
||||
"collaborators_url": "https://api.github.com/repos/octo-org/octo-repo/collaborators{/collaborator}",
|
||||
"teams_url": "https://api.github.com/repos/octo-org/octo-repo/teams",
|
||||
"hooks_url": "https://api.github.com/repos/octo-org/octo-repo/hooks",
|
||||
"issue_events_url": "https://api.github.com/repos/octo-org/octo-repo/issues/events{/number}",
|
||||
"events_url": "https://api.github.com/repos/octo-org/octo-repo/events",
|
||||
"assignees_url": "https://api.github.com/repos/octo-org/octo-repo/assignees{/user}",
|
||||
"branches_url": "https://api.github.com/repos/octo-org/octo-repo/branches{/branch}",
|
||||
"tags_url": "https://api.github.com/repos/octo-org/octo-repo/tags",
|
||||
"blobs_url": "https://api.github.com/repos/octo-org/octo-repo/git/blobs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/octo-org/octo-repo/git/tags{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/octo-org/octo-repo/git/refs{/sha}",
|
||||
"trees_url": "https://api.github.com/repos/octo-org/octo-repo/git/trees{/sha}",
|
||||
"statuses_url": "https://api.github.com/repos/octo-org/octo-repo/statuses/{sha}",
|
||||
"languages_url": "https://api.github.com/repos/octo-org/octo-repo/languages",
|
||||
"stargazers_url": "https://api.github.com/repos/octo-org/octo-repo/stargazers",
|
||||
"contributors_url": "https://api.github.com/repos/octo-org/octo-repo/contributors",
|
||||
"subscribers_url": "https://api.github.com/repos/octo-org/octo-repo/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/octo-org/octo-repo/subscription",
|
||||
"commits_url": "https://api.github.com/repos/octo-org/octo-repo/commits{/sha}",
|
||||
"git_commits_url": "https://api.github.com/repos/octo-org/octo-repo/git/commits{/sha}",
|
||||
"comments_url": "https://api.github.com/repos/octo-org/octo-repo/comments{/number}",
|
||||
"issue_comment_url": "https://api.github.com/repos/octo-org/octo-repo/issues/comments{/number}",
|
||||
"contents_url": "https://api.github.com/repos/octo-org/octo-repo/contents/{+path}",
|
||||
"compare_url": "https://api.github.com/repos/octo-org/octo-repo/compare/{base}...{head}",
|
||||
"merges_url": "https://api.github.com/repos/octo-org/octo-repo/merges",
|
||||
"archive_url": "https://api.github.com/repos/octo-org/octo-repo/{archive_format}{/ref}",
|
||||
"downloads_url": "https://api.github.com/repos/octo-org/octo-repo/downloads",
|
||||
"issues_url": "https://api.github.com/repos/octo-org/octo-repo/issues{/number}",
|
||||
"pulls_url": "https://api.github.com/repos/octo-org/octo-repo/pulls{/number}",
|
||||
"milestones_url": "https://api.github.com/repos/octo-org/octo-repo/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/octo-org/octo-repo/notifications{?since,all,participating}",
|
||||
"labels_url": "https://api.github.com/repos/octo-org/octo-repo/labels{/name}",
|
||||
"releases_url": "https://api.github.com/repos/octo-org/octo-repo/releases{/id}",
|
||||
"deployments_url": "https://api.github.com/repos/octo-org/octo-repo/deployments"
|
||||
}
|
||||
}
|
||||
20
packages/ui/src/github/action/index.stories.tsx
Normal file
20
packages/ui/src/github/action/index.stories.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
import { Action } from './index';
|
||||
import action from './data.json';
|
||||
|
||||
const meta = {
|
||||
title: 'GitHub/Action',
|
||||
component: Action,
|
||||
} satisfies Meta<typeof Action>;
|
||||
|
||||
type Story = StoryFn<typeof Action>;
|
||||
|
||||
const Normal: Story = {
|
||||
args: {
|
||||
action: action,
|
||||
onPress: () => {},
|
||||
},
|
||||
} as any;
|
||||
|
||||
export { Normal };
|
||||
export default meta;
|
||||
56
packages/ui/src/github/action/index.tsx
Normal file
56
packages/ui/src/github/action/index.tsx
Normal file
@@ -0,0 +1,56 @@
|
||||
import { useCallback } from 'react';
|
||||
import { GithubTypes } from '@refocus/sdk';
|
||||
import {
|
||||
IoCheckmarkDoneCircleOutline,
|
||||
IoCloseCircleOutline,
|
||||
} from 'react-icons/io5';
|
||||
import { RxTimer } from 'react-icons/rx';
|
||||
import { FiPlayCircle } from 'react-icons/fi';
|
||||
import { GoQuestion } from 'react-icons/go';
|
||||
import { Avatar, Card, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
|
||||
type ActionProps = {
|
||||
action: GithubTypes.WorkflowRun;
|
||||
onPress?: (action: GithubTypes.WorkflowRun) => void;
|
||||
};
|
||||
|
||||
const getIcon = (status: string | null) => {
|
||||
switch (status) {
|
||||
case 'success':
|
||||
return <IoCheckmarkDoneCircleOutline size={48} color="green" />;
|
||||
case 'failure':
|
||||
return <IoCloseCircleOutline size={48} color="red" />;
|
||||
case 'in_progress':
|
||||
return <FiPlayCircle size={48} />;
|
||||
case 'queued':
|
||||
return <RxTimer size={48} />;
|
||||
default:
|
||||
return <GoQuestion size={48} />;
|
||||
}
|
||||
};
|
||||
|
||||
const Action: React.FC<ActionProps> = ({ action, onPress }) => {
|
||||
const onPressHandler = useCallback(() => {
|
||||
onPress?.(action);
|
||||
}, [action, onPress]);
|
||||
return (
|
||||
<Card $fr $items="center" $p="md" $gap="md" onClick={onPressHandler}>
|
||||
<Avatar
|
||||
url={action.actor?.avatar_url}
|
||||
name={action.actor?.name || action.actor?.login}
|
||||
decal={`#${action.run_attempt}`}
|
||||
/>
|
||||
<View $fc $f={1}>
|
||||
<Typography variant="overline">
|
||||
{action.name} - {action.actor?.name || action.actor?.login}
|
||||
</Typography>
|
||||
<Typography variant="title">{action.display_title}</Typography>
|
||||
<Typography variant="subtitle">{action.status}</Typography>
|
||||
</View>
|
||||
<View>{getIcon(action.status)}</View>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { Action };
|
||||
5
packages/ui/src/github/index.ts
Normal file
5
packages/ui/src/github/index.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
export * from './notification';
|
||||
export * from './profile';
|
||||
export * from './pull-request';
|
||||
export * from './login';
|
||||
export * from './not-logged-in';
|
||||
35
packages/ui/src/github/login/index.tsx
Normal file
35
packages/ui/src/github/login/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { GithubLogin as GithubLoginComponent } from '@refocus/sdk';
|
||||
import { SiGithub } from 'react-icons/si';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button, Dialog, View } from '../../base';
|
||||
|
||||
const GithubLogin: GithubLoginComponent = ({ setToken, cancel }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const save = useCallback(() => {
|
||||
setToken(value);
|
||||
}, [setToken, value]);
|
||||
return (
|
||||
<Dialog open={true}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<View $fc $gap="md">
|
||||
<View
|
||||
as="input"
|
||||
$u
|
||||
placeholder="Personal Access Token"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
<Dialog.Buttons>
|
||||
<Button icon={<SiGithub />} onClick={save} title="Save" />
|
||||
</Dialog.Buttons>
|
||||
</View>
|
||||
<Dialog.CloseButton onClick={cancel} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export { GithubLogin };
|
||||
26
packages/ui/src/github/not-logged-in/index.tsx
Normal file
26
packages/ui/src/github/not-logged-in/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useGithub, useWidget, useWidgetId } from '@refocus/sdk';
|
||||
import { SiGithub } from 'react-icons/si';
|
||||
import { Button, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
const Description = styled(Typography)`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const NotLoggedIn: React.FC = () => {
|
||||
const { login } = useGithub();
|
||||
const type = useWidgetId();
|
||||
const widget = useWidget(type);
|
||||
|
||||
return (
|
||||
<View $p="md" $fc $items="center" $gap="md">
|
||||
<Description>
|
||||
You need to be logged in to Github to see {widget?.name}
|
||||
</Description>
|
||||
<Button icon={<SiGithub />} onClick={login} title="Login" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotLoggedIn };
|
||||
22
packages/ui/src/github/notification/index.tsx
Normal file
22
packages/ui/src/github/notification/index.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import { Card } from '../../base/card';
|
||||
import { AsyncResponse } from '../../utils/types';
|
||||
import { Octokit } from 'octokit';
|
||||
|
||||
type GithubNotification = {
|
||||
notification: AsyncResponse<
|
||||
Octokit['rest']['activity']['listNotificationsForAuthenticatedUser']
|
||||
>['data'][0];
|
||||
};
|
||||
|
||||
const GithubNotification: React.FC<GithubNotification> = ({ notification }) => {
|
||||
return (
|
||||
<Card>
|
||||
<div>{notification.repository.full_name}</div>
|
||||
<div>{notification.subject.title}</div>
|
||||
<div>{notification.reason}</div>
|
||||
<div>{notification.updated_at}</div>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { GithubNotification };
|
||||
45
packages/ui/src/github/profile/index.stories.tsx
Normal file
45
packages/ui/src/github/profile/index.stories.tsx
Normal file
@@ -0,0 +1,45 @@
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
import { Profile } from './index';
|
||||
import { GithubTypes } from '@refocus/sdk';
|
||||
|
||||
const meta = {
|
||||
title: 'GitHub/Profile',
|
||||
component: Profile,
|
||||
} satisfies Meta<typeof Profile>;
|
||||
|
||||
type Story = StoryFn<typeof Profile>;
|
||||
|
||||
type ProfileData = Partial<GithubTypes.Profile>;
|
||||
|
||||
const profile: ProfileData = {
|
||||
name: 'John Doe',
|
||||
avatar_url: 'https://avatars.githubusercontent.com/u/1?v=4',
|
||||
login: 'johndoe',
|
||||
};
|
||||
|
||||
const Normal: Story = {
|
||||
args: {
|
||||
profile,
|
||||
},
|
||||
} as any;
|
||||
|
||||
const WithoutName: Story = {
|
||||
args: {
|
||||
profile: {
|
||||
...profile,
|
||||
name: undefined,
|
||||
} as GithubTypes.Profile,
|
||||
},
|
||||
} as any;
|
||||
|
||||
const WithoutImage: Story = {
|
||||
args: {
|
||||
profile: {
|
||||
...profile,
|
||||
avatar_url: undefined,
|
||||
},
|
||||
},
|
||||
} as any;
|
||||
|
||||
export { Normal, WithoutName, WithoutImage };
|
||||
export default meta;
|
||||
33
packages/ui/src/github/profile/index.tsx
Normal file
33
packages/ui/src/github/profile/index.tsx
Normal file
@@ -0,0 +1,33 @@
|
||||
import { GithubTypes } from '@refocus/sdk';
|
||||
import { useCallback } from 'react';
|
||||
import { Avatar, Card, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
import { LuGithub } from 'react-icons/lu';
|
||||
|
||||
type ProfileProps = {
|
||||
profile: GithubTypes.Profile;
|
||||
onPress?: (profile: GithubTypes.Profile) => void;
|
||||
};
|
||||
|
||||
const Profile: React.FC<ProfileProps> = ({ profile, onPress }) => {
|
||||
const onPressHandler = useCallback(() => {
|
||||
onPress?.(profile);
|
||||
}, [onPress, profile]);
|
||||
return (
|
||||
<Card $fr $items="center" $gap="md" $p="md" onClick={onPressHandler}>
|
||||
<Avatar
|
||||
decal={<LuGithub />}
|
||||
url={profile.avatar_url}
|
||||
name={profile.name || profile.login}
|
||||
/>
|
||||
<View $fr $fc>
|
||||
<Typography variant="title">{profile.name || profile.login}</Typography>
|
||||
{profile.name && profile.name !== profile.login && (
|
||||
<Typography variant="subtitle">{profile.login}</Typography>
|
||||
)}
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { Profile };
|
||||
536
packages/ui/src/github/pull-request/data.json
Normal file
536
packages/ui/src/github/pull-request/data.json
Normal file
@@ -0,0 +1,536 @@
|
||||
{
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347",
|
||||
"id": 1,
|
||||
"node_id": "MDExOlB1bGxSZXF1ZXN0MQ==",
|
||||
"html_url": "https://github.com/octocat/Hello-World/pull/1347",
|
||||
"diff_url": "https://github.com/octocat/Hello-World/pull/1347.diff",
|
||||
"patch_url": "https://github.com/octocat/Hello-World/pull/1347.patch",
|
||||
"issue_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347",
|
||||
"commits_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits",
|
||||
"review_comments_url": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments",
|
||||
"review_comment_url": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}",
|
||||
"comments_url": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments",
|
||||
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"number": 1347,
|
||||
"state": "open",
|
||||
"locked": true,
|
||||
"title": "Amazing new feature",
|
||||
"user": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"body": "Please pull these awesome changes in!",
|
||||
"labels": [
|
||||
{
|
||||
"id": 208045946,
|
||||
"node_id": "MDU6TGFiZWwyMDgwNDU5NDY=",
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World/labels/bug",
|
||||
"name": "bug",
|
||||
"description": "Something isn't working",
|
||||
"color": "f29513",
|
||||
"default": true
|
||||
}
|
||||
],
|
||||
"milestone": {
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World/milestones/1",
|
||||
"html_url": "https://github.com/octocat/Hello-World/milestones/v1.0",
|
||||
"labels_url": "https://api.github.com/repos/octocat/Hello-World/milestones/1/labels",
|
||||
"id": 1002604,
|
||||
"node_id": "MDk6TWlsZXN0b25lMTAwMjYwNA==",
|
||||
"number": 1,
|
||||
"state": "open",
|
||||
"title": "v1.0",
|
||||
"description": "Tracking milestone for version 1.0",
|
||||
"creator": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"open_issues": 4,
|
||||
"closed_issues": 8,
|
||||
"created_at": "2011-04-10T20:09:31Z",
|
||||
"updated_at": "2014-03-03T18:58:10Z",
|
||||
"closed_at": "2013-02-12T13:22:01Z",
|
||||
"due_on": "2012-10-09T23:39:01Z"
|
||||
},
|
||||
"active_lock_reason": "too heated",
|
||||
"created_at": "2011-01-26T19:01:12Z",
|
||||
"updated_at": "2011-01-26T19:01:12Z",
|
||||
"closed_at": "2011-01-26T19:01:12Z",
|
||||
"merged_at": "2011-01-26T19:01:12Z",
|
||||
"merge_commit_sha": "e5bd3914e2e596debea16f433f57875b5b90bcd6",
|
||||
"assignee": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"assignees": [
|
||||
{
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
{
|
||||
"login": "hubot",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/hubot_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/hubot",
|
||||
"html_url": "https://github.com/hubot",
|
||||
"followers_url": "https://api.github.com/users/hubot/followers",
|
||||
"following_url": "https://api.github.com/users/hubot/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/hubot/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/hubot/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/hubot/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/hubot/orgs",
|
||||
"repos_url": "https://api.github.com/users/hubot/repos",
|
||||
"events_url": "https://api.github.com/users/hubot/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/hubot/received_events",
|
||||
"type": "User",
|
||||
"site_admin": true
|
||||
}
|
||||
],
|
||||
"requested_reviewers": [
|
||||
{
|
||||
"login": "other_user",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/other_user_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/other_user",
|
||||
"html_url": "https://github.com/other_user",
|
||||
"followers_url": "https://api.github.com/users/other_user/followers",
|
||||
"following_url": "https://api.github.com/users/other_user/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/other_user/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/other_user/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/other_user/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/other_user/orgs",
|
||||
"repos_url": "https://api.github.com/users/other_user/repos",
|
||||
"events_url": "https://api.github.com/users/other_user/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/other_user/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
}
|
||||
],
|
||||
"requested_teams": [
|
||||
{
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VGVhbTE=",
|
||||
"url": "https://api.github.com/teams/1",
|
||||
"html_url": "https://github.com/orgs/github/teams/justice-league",
|
||||
"name": "Justice League",
|
||||
"slug": "justice-league",
|
||||
"description": "A great team.",
|
||||
"privacy": "closed",
|
||||
"notification_setting": "notifications_enabled",
|
||||
"permission": "admin",
|
||||
"members_url": "https://api.github.com/teams/1/members{/member}",
|
||||
"repositories_url": "https://api.github.com/teams/1/repos"
|
||||
}
|
||||
],
|
||||
"head": {
|
||||
"label": "octocat:new-topic",
|
||||
"ref": "new-topic",
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"user": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 1296269,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
|
||||
"name": "Hello-World",
|
||||
"full_name": "octocat/Hello-World",
|
||||
"owner": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/octocat/Hello-World",
|
||||
"description": "This your first repo!",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World",
|
||||
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
|
||||
"events_url": "https://api.github.com/repos/octocat/Hello-World/events",
|
||||
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
|
||||
"git_url": "git:github.com/octocat/Hello-World.git",
|
||||
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
|
||||
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
|
||||
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
|
||||
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
||||
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
|
||||
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
|
||||
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
|
||||
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
|
||||
"clone_url": "https://github.com/octocat/Hello-World.git",
|
||||
"mirror_url": "git:git.example.com/octocat/Hello-World",
|
||||
"hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
|
||||
"svn_url": "https://svn.github.com/octocat/Hello-World",
|
||||
"homepage": "https://github.com",
|
||||
"language": null,
|
||||
"forks_count": 9,
|
||||
"stargazers_count": 80,
|
||||
"watchers_count": 80,
|
||||
"size": 108,
|
||||
"default_branch": "master",
|
||||
"open_issues_count": 0,
|
||||
"topics": [
|
||||
"octocat",
|
||||
"atom",
|
||||
"electron",
|
||||
"api"
|
||||
],
|
||||
"has_issues": true,
|
||||
"has_projects": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"pushed_at": "2011-01-26T19:06:43Z",
|
||||
"created_at": "2011-01-26T19:01:12Z",
|
||||
"updated_at": "2011-01-26T19:14:43Z",
|
||||
"permissions": {
|
||||
"admin": false,
|
||||
"push": false,
|
||||
"pull": true
|
||||
},
|
||||
"allow_rebase_merge": true,
|
||||
"temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O",
|
||||
"allow_squash_merge": true,
|
||||
"allow_merge_commit": true,
|
||||
"allow_forking": true,
|
||||
"forks": 123,
|
||||
"open_issues": 123,
|
||||
"license": {
|
||||
"key": "mit",
|
||||
"name": "MIT License",
|
||||
"url": "https://api.github.com/licenses/mit",
|
||||
"spdx_id": "MIT",
|
||||
"node_id": "MDc6TGljZW5zZW1pdA=="
|
||||
},
|
||||
"watchers": 123
|
||||
}
|
||||
},
|
||||
"base": {
|
||||
"label": "octocat:master",
|
||||
"ref": "master",
|
||||
"sha": "6dcb09b5b57875f334f61aebed695e2e4193db5e",
|
||||
"user": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"repo": {
|
||||
"id": 1296269,
|
||||
"node_id": "MDEwOlJlcG9zaXRvcnkxMjk2MjY5",
|
||||
"name": "Hello-World",
|
||||
"full_name": "octocat/Hello-World",
|
||||
"owner": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"private": false,
|
||||
"html_url": "https://github.com/octocat/Hello-World",
|
||||
"description": "This your first repo!",
|
||||
"fork": false,
|
||||
"url": "https://api.github.com/repos/octocat/Hello-World",
|
||||
"archive_url": "https://api.github.com/repos/octocat/Hello-World/{archive_format}{/ref}",
|
||||
"assignees_url": "https://api.github.com/repos/octocat/Hello-World/assignees{/user}",
|
||||
"blobs_url": "https://api.github.com/repos/octocat/Hello-World/git/blobs{/sha}",
|
||||
"branches_url": "https://api.github.com/repos/octocat/Hello-World/branches{/branch}",
|
||||
"collaborators_url": "https://api.github.com/repos/octocat/Hello-World/collaborators{/collaborator}",
|
||||
"comments_url": "https://api.github.com/repos/octocat/Hello-World/comments{/number}",
|
||||
"commits_url": "https://api.github.com/repos/octocat/Hello-World/commits{/sha}",
|
||||
"compare_url": "https://api.github.com/repos/octocat/Hello-World/compare/{base}...{head}",
|
||||
"contents_url": "https://api.github.com/repos/octocat/Hello-World/contents/{+path}",
|
||||
"contributors_url": "https://api.github.com/repos/octocat/Hello-World/contributors",
|
||||
"deployments_url": "https://api.github.com/repos/octocat/Hello-World/deployments",
|
||||
"downloads_url": "https://api.github.com/repos/octocat/Hello-World/downloads",
|
||||
"events_url": "https://api.github.com/repos/octocat/Hello-World/events",
|
||||
"forks_url": "https://api.github.com/repos/octocat/Hello-World/forks",
|
||||
"git_commits_url": "https://api.github.com/repos/octocat/Hello-World/git/commits{/sha}",
|
||||
"git_refs_url": "https://api.github.com/repos/octocat/Hello-World/git/refs{/sha}",
|
||||
"git_tags_url": "https://api.github.com/repos/octocat/Hello-World/git/tags{/sha}",
|
||||
"git_url": "git:github.com/octocat/Hello-World.git",
|
||||
"issue_comment_url": "https://api.github.com/repos/octocat/Hello-World/issues/comments{/number}",
|
||||
"issue_events_url": "https://api.github.com/repos/octocat/Hello-World/issues/events{/number}",
|
||||
"issues_url": "https://api.github.com/repos/octocat/Hello-World/issues{/number}",
|
||||
"keys_url": "https://api.github.com/repos/octocat/Hello-World/keys{/key_id}",
|
||||
"labels_url": "https://api.github.com/repos/octocat/Hello-World/labels{/name}",
|
||||
"languages_url": "https://api.github.com/repos/octocat/Hello-World/languages",
|
||||
"merges_url": "https://api.github.com/repos/octocat/Hello-World/merges",
|
||||
"milestones_url": "https://api.github.com/repos/octocat/Hello-World/milestones{/number}",
|
||||
"notifications_url": "https://api.github.com/repos/octocat/Hello-World/notifications{?since,all,participating}",
|
||||
"pulls_url": "https://api.github.com/repos/octocat/Hello-World/pulls{/number}",
|
||||
"releases_url": "https://api.github.com/repos/octocat/Hello-World/releases{/id}",
|
||||
"ssh_url": "git@github.com:octocat/Hello-World.git",
|
||||
"stargazers_url": "https://api.github.com/repos/octocat/Hello-World/stargazers",
|
||||
"statuses_url": "https://api.github.com/repos/octocat/Hello-World/statuses/{sha}",
|
||||
"subscribers_url": "https://api.github.com/repos/octocat/Hello-World/subscribers",
|
||||
"subscription_url": "https://api.github.com/repos/octocat/Hello-World/subscription",
|
||||
"tags_url": "https://api.github.com/repos/octocat/Hello-World/tags",
|
||||
"teams_url": "https://api.github.com/repos/octocat/Hello-World/teams",
|
||||
"trees_url": "https://api.github.com/repos/octocat/Hello-World/git/trees{/sha}",
|
||||
"clone_url": "https://github.com/octocat/Hello-World.git",
|
||||
"mirror_url": "git:git.example.com/octocat/Hello-World",
|
||||
"hooks_url": "https://api.github.com/repos/octocat/Hello-World/hooks",
|
||||
"svn_url": "https://svn.github.com/octocat/Hello-World",
|
||||
"homepage": "https://github.com",
|
||||
"language": null,
|
||||
"forks_count": 9,
|
||||
"stargazers_count": 80,
|
||||
"watchers_count": 80,
|
||||
"size": 108,
|
||||
"default_branch": "master",
|
||||
"open_issues_count": 0,
|
||||
"topics": [
|
||||
"octocat",
|
||||
"atom",
|
||||
"electron",
|
||||
"api"
|
||||
],
|
||||
"has_issues": true,
|
||||
"has_projects": true,
|
||||
"has_wiki": true,
|
||||
"has_pages": false,
|
||||
"has_downloads": true,
|
||||
"has_discussions": false,
|
||||
"archived": false,
|
||||
"disabled": false,
|
||||
"pushed_at": "2011-01-26T19:06:43Z",
|
||||
"created_at": "2011-01-26T19:01:12Z",
|
||||
"updated_at": "2011-01-26T19:14:43Z",
|
||||
"permissions": {
|
||||
"admin": false,
|
||||
"push": false,
|
||||
"pull": true
|
||||
},
|
||||
"allow_rebase_merge": true,
|
||||
"temp_clone_token": "ABTLWHOULUVAXGTRYU7OC2876QJ2O",
|
||||
"allow_squash_merge": true,
|
||||
"allow_merge_commit": true,
|
||||
"forks": 123,
|
||||
"open_issues": 123,
|
||||
"license": {
|
||||
"key": "mit",
|
||||
"name": "MIT License",
|
||||
"url": "https://api.github.com/licenses/mit",
|
||||
"spdx_id": "MIT",
|
||||
"node_id": "MDc6TGljZW5zZW1pdA=="
|
||||
},
|
||||
"watchers": 123
|
||||
}
|
||||
},
|
||||
"_links": {
|
||||
"self": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347"
|
||||
},
|
||||
"html": {
|
||||
"href": "https://github.com/octocat/Hello-World/pull/1347"
|
||||
},
|
||||
"issue": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/issues/1347"
|
||||
},
|
||||
"comments": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/issues/1347/comments"
|
||||
},
|
||||
"review_comments": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/comments"
|
||||
},
|
||||
"review_comment": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/comments{/number}"
|
||||
},
|
||||
"commits": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/pulls/1347/commits"
|
||||
},
|
||||
"statuses": {
|
||||
"href": "https://api.github.com/repos/octocat/Hello-World/statuses/6dcb09b5b57875f334f61aebed695e2e4193db5e"
|
||||
}
|
||||
},
|
||||
"author_association": "OWNER",
|
||||
"auto_merge": null,
|
||||
"draft": false,
|
||||
"merged": false,
|
||||
"mergeable": true,
|
||||
"rebaseable": true,
|
||||
"mergeable_state": "clean",
|
||||
"merged_by": {
|
||||
"login": "octocat",
|
||||
"id": 1,
|
||||
"node_id": "MDQ6VXNlcjE=",
|
||||
"avatar_url": "https://github.com/images/error/octocat_happy.gif",
|
||||
"gravatar_id": "",
|
||||
"url": "https://api.github.com/users/octocat",
|
||||
"html_url": "https://github.com/octocat",
|
||||
"followers_url": "https://api.github.com/users/octocat/followers",
|
||||
"following_url": "https://api.github.com/users/octocat/following{/other_user}",
|
||||
"gists_url": "https://api.github.com/users/octocat/gists{/gist_id}",
|
||||
"starred_url": "https://api.github.com/users/octocat/starred{/owner}{/repo}",
|
||||
"subscriptions_url": "https://api.github.com/users/octocat/subscriptions",
|
||||
"organizations_url": "https://api.github.com/users/octocat/orgs",
|
||||
"repos_url": "https://api.github.com/users/octocat/repos",
|
||||
"events_url": "https://api.github.com/users/octocat/events{/privacy}",
|
||||
"received_events_url": "https://api.github.com/users/octocat/received_events",
|
||||
"type": "User",
|
||||
"site_admin": false
|
||||
},
|
||||
"comments": 10,
|
||||
"review_comments": 0,
|
||||
"maintainer_can_modify": true,
|
||||
"commits": 3,
|
||||
"additions": 100,
|
||||
"deletions": 3,
|
||||
"changed_files": 5
|
||||
}
|
||||
20
packages/ui/src/github/pull-request/index.stories.tsx
Normal file
20
packages/ui/src/github/pull-request/index.stories.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import type { StoryFn, Meta } from '@storybook/react';
|
||||
import { PullRequest } from './index';
|
||||
import pullRequest from './data.json';
|
||||
|
||||
const meta = {
|
||||
title: 'GitHub/Pull Request',
|
||||
component: PullRequest,
|
||||
} satisfies Meta<typeof PullRequest>;
|
||||
|
||||
type Story = StoryFn<typeof PullRequest>;
|
||||
|
||||
const Normal: Story = {
|
||||
args: {
|
||||
pullRequest: pullRequest,
|
||||
onPress: () => {},
|
||||
},
|
||||
} as any;
|
||||
|
||||
export { Normal };
|
||||
export default meta;
|
||||
32
packages/ui/src/github/pull-request/index.tsx
Normal file
32
packages/ui/src/github/pull-request/index.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useCallback } from 'react';
|
||||
import { GithubTypes } from '@refocus/sdk';
|
||||
import { Avatar, Card, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
|
||||
type PullRequestProps = {
|
||||
pullRequest: GithubTypes.PullRequest;
|
||||
onPress?: (oullRequest: GithubTypes.PullRequest) => void;
|
||||
};
|
||||
|
||||
const PullRequest: React.FC<PullRequestProps> = ({ pullRequest, onPress }) => {
|
||||
const onPressHandler = useCallback(() => {
|
||||
onPress?.(pullRequest);
|
||||
}, [pullRequest, onPress]);
|
||||
return (
|
||||
<Card $fr $items="center" $p="md" $gap="md" onClick={onPressHandler}>
|
||||
<Avatar
|
||||
url={pullRequest.user.avatar_url}
|
||||
decal={pullRequest.state === 'open' ? 'open' : 'closed'}
|
||||
/>
|
||||
<View $fc>
|
||||
<Typography variant="overline">
|
||||
{pullRequest.head.repo?.full_name}
|
||||
{' - '}#{pullRequest.number}
|
||||
</Typography>
|
||||
<Typography variant="title">{pullRequest.title}</Typography>
|
||||
</View>
|
||||
</Card>
|
||||
);
|
||||
};
|
||||
|
||||
export { PullRequest };
|
||||
9
packages/ui/src/index.ts
Normal file
9
packages/ui/src/index.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
export * from './base';
|
||||
export * as Github from './github';
|
||||
export * as Linear from './linear';
|
||||
export * as Interface from './interface';
|
||||
export * as Chat from './chat';
|
||||
export * as Slack from './slack';
|
||||
export { FocusProvider } from './provider';
|
||||
export { UIProvider } from './theme/provider';
|
||||
export { Typography, styles } from './typography';
|
||||
84
packages/ui/src/interface/add-from-url/index.tsx
Normal file
84
packages/ui/src/interface/add-from-url/index.tsx
Normal file
@@ -0,0 +1,84 @@
|
||||
import { useState, useCallback, useEffect } from 'react';
|
||||
import { Card, Dialog, View } from '../../base';
|
||||
import { useGetWidgetsFromUrl } from '@refocus/sdk';
|
||||
import { Typography } from '../../typography';
|
||||
|
||||
type AddWidgetFromUrlProps = {
|
||||
onCreate: (name: string, data: any) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
const Root: React.FC<AddWidgetFromUrlProps> = ({ onCreate, children }) => {
|
||||
const [url, setUrl] = useState('');
|
||||
const [widgets, update] = useGetWidgetsFromUrl();
|
||||
|
||||
const handleSave = useCallback(
|
||||
(id: string, data: any) => {
|
||||
onCreate(id, data);
|
||||
},
|
||||
[onCreate],
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
const parsed = new URL(url, 'http://example.com');
|
||||
if (parsed.host === 'example.com') {
|
||||
return;
|
||||
}
|
||||
update(parsed);
|
||||
}, [url, update]);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
{children}
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<Dialog.CloseButton />
|
||||
<View $fc $gap="sm">
|
||||
<View $fr>
|
||||
<View
|
||||
as="input"
|
||||
$f={1}
|
||||
$u
|
||||
placeholder="URL"
|
||||
onChange={(e) => setUrl(e.target.value)}
|
||||
value={url}
|
||||
/>
|
||||
</View>
|
||||
{widgets.map((widget) => (
|
||||
<View key={widget.id}>
|
||||
<Dialog.Close>
|
||||
<Card
|
||||
$fr
|
||||
$items="center"
|
||||
$gap="md"
|
||||
$p="md"
|
||||
onClick={() => handleSave(widget.id, widget.data)}
|
||||
>
|
||||
<View>
|
||||
<Typography variant="header">{widget.icon}</Typography>
|
||||
</View>
|
||||
<View>
|
||||
<Typography variant="title">{widget.name}</Typography>
|
||||
{widget.description && (
|
||||
<Typography variant="body">
|
||||
{widget.description}
|
||||
</Typography>
|
||||
)}
|
||||
</View>
|
||||
</Card>
|
||||
</Dialog.Close>
|
||||
</View>
|
||||
))}
|
||||
</View>
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const AddWidgetFromUrl = Object.assign(Root, {
|
||||
Trigger: Dialog.Trigger,
|
||||
});
|
||||
|
||||
export { AddWidgetFromUrl };
|
||||
66
packages/ui/src/interface/app/index.tsx
Normal file
66
packages/ui/src/interface/app/index.tsx
Normal file
@@ -0,0 +1,66 @@
|
||||
import {
|
||||
useAddBoard,
|
||||
useBoards,
|
||||
useRemoveBoard,
|
||||
useSelectBoard,
|
||||
useSelectedBoard,
|
||||
} from '@refocus/sdk';
|
||||
import { IoAddCircleOutline } from 'react-icons/io5';
|
||||
import styled from 'styled-components';
|
||||
import { View } from '../../base';
|
||||
import { Board } from '../board';
|
||||
import { Tabs } from '../../base/tabs';
|
||||
import { useCallback } from 'react';
|
||||
|
||||
const NotificationBar = styled(View)``;
|
||||
|
||||
const App: React.FC = () => {
|
||||
const boards = useBoards();
|
||||
const selected = useSelectedBoard();
|
||||
const selectBoard = useSelectBoard();
|
||||
const addBoardAction = useAddBoard();
|
||||
const removeBoard = useRemoveBoard();
|
||||
|
||||
const addBoard = useCallback(() => {
|
||||
const name = prompt('Board name?');
|
||||
if (!name) {
|
||||
return;
|
||||
}
|
||||
addBoardAction(name);
|
||||
}, [addBoardAction]);
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View $f={1}>
|
||||
<Tabs value={selected} onValueChange={selectBoard}>
|
||||
<Tabs.List>
|
||||
{Object.entries(boards).map(([id, board]) => (
|
||||
<Tabs.Trigger key={id} value={id}>
|
||||
{board.name}
|
||||
<Tabs.Close onClick={() => removeBoard(id)} />
|
||||
</Tabs.Trigger>
|
||||
))}
|
||||
<View
|
||||
onClick={addBoard}
|
||||
$fr
|
||||
$justify="center"
|
||||
$items="center"
|
||||
$p="md"
|
||||
$bg="highlight100"
|
||||
>
|
||||
<IoAddCircleOutline size={16} />
|
||||
</View>
|
||||
</Tabs.List>
|
||||
{Object.entries(boards).map(([id, board]) => (
|
||||
<Tabs.Content key={id} value={id}>
|
||||
<Board id={id} board={board} />
|
||||
</Tabs.Content>
|
||||
))}
|
||||
</Tabs>
|
||||
</View>
|
||||
<NotificationBar></NotificationBar>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { App };
|
||||
62
packages/ui/src/interface/board/index.tsx
Normal file
62
packages/ui/src/interface/board/index.tsx
Normal file
@@ -0,0 +1,62 @@
|
||||
import {
|
||||
Board,
|
||||
useAddWidget,
|
||||
useRemoveWidget,
|
||||
useUpdateWidget,
|
||||
} from '@refocus/sdk';
|
||||
import { IoAddCircleOutline } from 'react-icons/io5';
|
||||
import { View } from '../../base';
|
||||
import { Widget } from '../widget';
|
||||
import { AddWidgetFromUrl } from '../add-from-url';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
type BoardProps = {
|
||||
board: Board;
|
||||
id: string;
|
||||
};
|
||||
|
||||
const Wrapper = styled(View)`
|
||||
flex-wrap: wrap;
|
||||
`;
|
||||
|
||||
const ItemWrapper = styled(View)`
|
||||
max-width: 400px;
|
||||
overflow-y: auto;
|
||||
max-height: 500px;
|
||||
`;
|
||||
|
||||
const Board: React.FC<BoardProps> = ({ board, id }) => {
|
||||
const setWidgetData = useUpdateWidget();
|
||||
const removeWidget = useRemoveWidget();
|
||||
const addWidget = useAddWidget();
|
||||
|
||||
return (
|
||||
<View>
|
||||
<View $p="md">
|
||||
<AddWidgetFromUrl onCreate={(type, data) => addWidget(id, type, data)}>
|
||||
<AddWidgetFromUrl.Trigger>
|
||||
<View $fr $items="center" $p="sm" $gap="sm">
|
||||
<IoAddCircleOutline />
|
||||
Add from URL
|
||||
</View>
|
||||
</AddWidgetFromUrl.Trigger>
|
||||
</AddWidgetFromUrl>
|
||||
</View>
|
||||
<Wrapper $fr>
|
||||
{Object.entries(board.widgets).map(([widgetId, widget]) => (
|
||||
<ItemWrapper key={widgetId}>
|
||||
<Widget
|
||||
key={widgetId}
|
||||
id={widget.type}
|
||||
data={widget.data}
|
||||
setData={(data) => setWidgetData(id, widgetId, data)}
|
||||
onRemove={() => removeWidget(id, widgetId)}
|
||||
/>
|
||||
</ItemWrapper>
|
||||
))}
|
||||
</Wrapper>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { Board };
|
||||
81
packages/ui/src/interface/create-widget/index.tsx
Normal file
81
packages/ui/src/interface/create-widget/index.tsx
Normal file
@@ -0,0 +1,81 @@
|
||||
import { useState, useCallback } from 'react';
|
||||
import { Dialog } from '../../base';
|
||||
import {
|
||||
WidgetEditor,
|
||||
WidgetProvider,
|
||||
useWidgets,
|
||||
} from '@refocus/sdk';
|
||||
|
||||
type CreateWidgetProps = {
|
||||
onCreate: (name: string, data: any) => void;
|
||||
children?: React.ReactNode;
|
||||
};
|
||||
|
||||
type WidgetEditorProps = {
|
||||
id: string;
|
||||
onSave: (data: any) => void;
|
||||
};
|
||||
|
||||
const Editor: React.FC<WidgetEditorProps> = ({ id, onSave }) => {
|
||||
return (
|
||||
<WidgetProvider id={id}>
|
||||
<WidgetEditor onSave={onSave} />
|
||||
</WidgetProvider>
|
||||
);
|
||||
};
|
||||
|
||||
type WidgetSelectorProps = {
|
||||
onSelect: (id: string) => void;
|
||||
};
|
||||
|
||||
const WidgetSelector: React.FC<WidgetSelectorProps> = ({ onSelect }) => {
|
||||
const widgets = useWidgets();
|
||||
|
||||
const handleSelect = useCallback(
|
||||
(id: string) => {
|
||||
onSelect(id);
|
||||
},
|
||||
[onSelect],
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
{widgets.map((widget) => (
|
||||
<button key={widget.id} onClick={() => handleSelect(widget.id)}>
|
||||
{widget.name}
|
||||
</button>
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const Root: React.FC<CreateWidgetProps> = ({ onCreate, children }) => {
|
||||
const [id, setId] = useState<string>('');
|
||||
|
||||
const handleSave = useCallback(
|
||||
(data: any) => {
|
||||
onCreate(id, data);
|
||||
},
|
||||
[id, onCreate],
|
||||
);
|
||||
|
||||
return (
|
||||
<Dialog>
|
||||
{children}
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<Dialog.CloseButton />
|
||||
{id && <Editor id={id} onSave={handleSave} />}
|
||||
{!id && <WidgetSelector onSelect={setId} />}
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
const CreateWidget = Object.assign(Root, {
|
||||
Trigger: Dialog.Trigger,
|
||||
});
|
||||
|
||||
export { CreateWidget };
|
||||
6
packages/ui/src/interface/index.ts
Normal file
6
packages/ui/src/interface/index.ts
Normal file
@@ -0,0 +1,6 @@
|
||||
export * from './create-widget';
|
||||
export * from './add-from-url';
|
||||
export * from './notifications';
|
||||
export * from './widget';
|
||||
export * from './board';
|
||||
export * from './app';
|
||||
26
packages/ui/src/interface/notifications/index.tsx
Normal file
26
packages/ui/src/interface/notifications/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useNotificationDismiss, useNotifications } from '@refocus/sdk';
|
||||
import { Card, List } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
|
||||
const Notifications: React.FC = () => {
|
||||
const notifications = useNotifications();
|
||||
const dismiss = useNotificationDismiss();
|
||||
|
||||
return (
|
||||
<List>
|
||||
{notifications.map((notification, index) => (
|
||||
<Card
|
||||
key={notification.id || index}
|
||||
onClick={() => dismiss(notification.id || '')}
|
||||
>
|
||||
{notification.title && (
|
||||
<Typography variant="title">{notification.title}</Typography>
|
||||
)}
|
||||
{notification.message}
|
||||
</Card>
|
||||
))}
|
||||
</List>
|
||||
);
|
||||
};
|
||||
|
||||
export { Notifications };
|
||||
96
packages/ui/src/interface/widget/index.tsx
Normal file
96
packages/ui/src/interface/widget/index.tsx
Normal file
@@ -0,0 +1,96 @@
|
||||
import styled, { useTheme } from 'styled-components';
|
||||
import {
|
||||
WidgetEditor,
|
||||
WidgetProvider,
|
||||
WidgetView,
|
||||
useWidget,
|
||||
} from '@refocus/sdk';
|
||||
import { VscTrash } from 'react-icons/vsc';
|
||||
import { CgMoreO } from 'react-icons/cg';
|
||||
import { Dialog, View } from '../../base';
|
||||
import { DropdownMenu } from '../../base';
|
||||
import { useCallback, useMemo, useState } from 'react';
|
||||
|
||||
type WidgetProps = {
|
||||
id: string;
|
||||
data: any;
|
||||
setData?: (data: any) => void;
|
||||
className?: string;
|
||||
onRemove?: () => void;
|
||||
};
|
||||
|
||||
const Wrapper = styled(View)`
|
||||
background: ${({ theme }) => theme.colors.bg.base};
|
||||
`;
|
||||
|
||||
const Widget: React.FC<WidgetProps> = ({
|
||||
id,
|
||||
data,
|
||||
setData,
|
||||
className,
|
||||
onRemove,
|
||||
}) => {
|
||||
const theme = useTheme();
|
||||
const [showEdit, setShowEdit] = useState(false);
|
||||
const widget = useWidget(id);
|
||||
const hasMenu = useMemo(
|
||||
() => !!(widget?.edit && setData) || onRemove,
|
||||
[widget, onRemove, setData],
|
||||
);
|
||||
const onSave = useCallback(
|
||||
(nextData: any) => {
|
||||
setData?.(nextData);
|
||||
setShowEdit(false);
|
||||
},
|
||||
[setData],
|
||||
);
|
||||
return (
|
||||
<WidgetProvider id={id} data={data} setData={setData}>
|
||||
<Wrapper className={className} $fr>
|
||||
<View $f={1}>
|
||||
<WidgetView />
|
||||
</View>
|
||||
<View $fc>
|
||||
{hasMenu && (
|
||||
<DropdownMenu>
|
||||
<DropdownMenu.Trigger>
|
||||
<View $p="sm">
|
||||
<CgMoreO />
|
||||
</View>
|
||||
</DropdownMenu.Trigger>
|
||||
<DropdownMenu.Portal>
|
||||
<DropdownMenu.Content alignOffset={50}>
|
||||
{!!onRemove && (
|
||||
<DropdownMenu.Item onClick={onRemove}>
|
||||
<DropdownMenu.Icon>
|
||||
<VscTrash color={theme?.colors.simple.red} />
|
||||
</DropdownMenu.Icon>
|
||||
Remove
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
{!!widget?.edit && !!setData && (
|
||||
<DropdownMenu.Item onClick={() => setShowEdit(true)}>
|
||||
Edit
|
||||
</DropdownMenu.Item>
|
||||
)}
|
||||
<DropdownMenu.Arrow />
|
||||
</DropdownMenu.Content>
|
||||
</DropdownMenu.Portal>
|
||||
</DropdownMenu>
|
||||
)}
|
||||
</View>
|
||||
</Wrapper>
|
||||
<Dialog open={showEdit} onOpenChange={setShowEdit}>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<Dialog.Title>Edit Widget</Dialog.Title>
|
||||
<Dialog.Description>
|
||||
<WidgetEditor onSave={onSave} />
|
||||
</Dialog.Description>
|
||||
</Dialog.Content>
|
||||
</Dialog>
|
||||
</WidgetProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { Widget };
|
||||
4
packages/ui/src/linear/index.ts
Normal file
4
packages/ui/src/linear/index.ts
Normal file
@@ -0,0 +1,4 @@
|
||||
export * from './issue';
|
||||
export * from './notification';
|
||||
export * from './login';
|
||||
export * from './not-logged-in';
|
||||
19
packages/ui/src/linear/issue/index.tsx
Normal file
19
packages/ui/src/linear/issue/index.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import type { Issue as IssueType } from '@linear/sdk';
|
||||
import { Link } from 'react-router-dom';
|
||||
import { IssueSearchResult } from '@linear/sdk/dist/_generated_documents';
|
||||
|
||||
type IssueProps = {
|
||||
issue: IssueType | IssueSearchResult;
|
||||
};
|
||||
const Issue: React.FC<IssueProps> = ({ issue }) => {
|
||||
return (
|
||||
<div>
|
||||
<Link to={`/linear/issue?id=${issue.id}`}>
|
||||
<h3 className="text-lg font-bold">{issue.title}</h3>
|
||||
</Link>
|
||||
{issue.description}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
export { Issue };
|
||||
35
packages/ui/src/linear/login/index.tsx
Normal file
35
packages/ui/src/linear/login/index.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import { LinearLogin as LinearLoginComponent } from '@refocus/sdk';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button, Dialog, View } from '../../base';
|
||||
import { SiLinear } from 'react-icons/si';
|
||||
|
||||
const LinearLogin: LinearLoginComponent = ({ setApiKey, cancel }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const save = useCallback(() => {
|
||||
setApiKey(value);
|
||||
}, [setApiKey, value]);
|
||||
return (
|
||||
<Dialog open={true}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<View $fc $gap="md">
|
||||
<View
|
||||
as="input"
|
||||
$u
|
||||
value={value}
|
||||
placeholder="API Token"
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
<Dialog.Buttons>
|
||||
<Button icon={<SiLinear />} onClick={save} title="Save" />
|
||||
</Dialog.Buttons>
|
||||
</View>
|
||||
<Dialog.CloseButton onClick={cancel} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export { LinearLogin };
|
||||
26
packages/ui/src/linear/not-logged-in/index.tsx
Normal file
26
packages/ui/src/linear/not-logged-in/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useLinear, useWidget, useWidgetId } from '@refocus/sdk';
|
||||
import { SiLinear } from 'react-icons/si';
|
||||
import { Button, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
const Description = styled(Typography)`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const NotLoggedIn: React.FC = () => {
|
||||
const { login } = useLinear();
|
||||
const type = useWidgetId();
|
||||
const widget = useWidget(type);
|
||||
|
||||
return (
|
||||
<View $p="md" $fc $items="center" $gap="md">
|
||||
<Description>
|
||||
You need to be logged in to Linear to see {widget?.name}
|
||||
</Description>
|
||||
<Button icon={<SiLinear />} onClick={login} title="Login" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotLoggedIn };
|
||||
16
packages/ui/src/linear/notification/index.tsx
Normal file
16
packages/ui/src/linear/notification/index.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { Card } from '../../base/card';
|
||||
import { AsyncResponse, Expand } from '../../utils/types';
|
||||
import { LinearClient } from '@linear/sdk';
|
||||
type LinearNotificationProps = {
|
||||
notification: Expand<
|
||||
AsyncResponse<LinearClient['notifications']>['nodes'][0]
|
||||
>;
|
||||
};
|
||||
|
||||
const LinearNotification: React.FC<LinearNotificationProps> = ({
|
||||
notification,
|
||||
}) => {
|
||||
return <Card>Hello</Card>;
|
||||
};
|
||||
|
||||
export { LinearNotification };
|
||||
52
packages/ui/src/provider.tsx
Normal file
52
packages/ui/src/provider.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import { DashboardProvider, Widget } from '@refocus/sdk';
|
||||
import { UIProvider } from './theme/provider';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { GithubLogin } from './github';
|
||||
import { LinearLogin } from './linear';
|
||||
import { SlackLogin } from './slack';
|
||||
|
||||
type FocusProviderProps = {
|
||||
children: React.ReactNode;
|
||||
widgets: Widget<any>[];
|
||||
};
|
||||
|
||||
const FocusProvider: React.FC<FocusProviderProps> = ({ children, widgets }) => {
|
||||
const save = useCallback((data: any) => {
|
||||
localStorage.setItem('boards', JSON.stringify(data));
|
||||
}, []);
|
||||
|
||||
const logins = useMemo(
|
||||
() => ({
|
||||
github: GithubLogin,
|
||||
linear: LinearLogin,
|
||||
slack: SlackLogin,
|
||||
}),
|
||||
[],
|
||||
);
|
||||
|
||||
const load = useCallback(() => {
|
||||
const data = localStorage.getItem('boards');
|
||||
if (data) {
|
||||
return JSON.parse(data);
|
||||
}
|
||||
return {
|
||||
boards: {},
|
||||
selected: undefined,
|
||||
};
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<UIProvider>
|
||||
<DashboardProvider
|
||||
load={load}
|
||||
save={save}
|
||||
widgets={widgets}
|
||||
logins={logins}
|
||||
>
|
||||
{children}
|
||||
</DashboardProvider>
|
||||
</UIProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { FocusProvider };
|
||||
2
packages/ui/src/slack/index.ts
Normal file
2
packages/ui/src/slack/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from './login';
|
||||
export * from './not-logged-in';
|
||||
34
packages/ui/src/slack/login/index.tsx
Normal file
34
packages/ui/src/slack/login/index.tsx
Normal file
@@ -0,0 +1,34 @@
|
||||
import { SlackLogin as SlackLoginComponent } from '@refocus/sdk';
|
||||
import { useCallback, useState } from 'react';
|
||||
import { Button, Dialog, View } from '../../base';
|
||||
|
||||
const SlackLogin: SlackLoginComponent = ({ setToken, cancel }) => {
|
||||
const [value, setValue] = useState('');
|
||||
const save = useCallback(() => {
|
||||
setToken(value);
|
||||
}, [setToken, value]);
|
||||
return (
|
||||
<Dialog open={true}>
|
||||
<Dialog.Portal>
|
||||
<Dialog.Overlay />
|
||||
<Dialog.Content>
|
||||
<View $fc $gap="md">
|
||||
<View
|
||||
as="input"
|
||||
$u
|
||||
placeholder="Token"
|
||||
value={value}
|
||||
onChange={(e) => setValue(e.target.value)}
|
||||
/>
|
||||
<Dialog.Buttons>
|
||||
<Button onClick={save} title="Save" />
|
||||
</Dialog.Buttons>
|
||||
</View>
|
||||
<Dialog.CloseButton onClick={cancel} />
|
||||
</Dialog.Content>
|
||||
</Dialog.Portal>
|
||||
</Dialog>
|
||||
);
|
||||
};
|
||||
|
||||
export { SlackLogin };
|
||||
26
packages/ui/src/slack/not-logged-in/index.tsx
Normal file
26
packages/ui/src/slack/not-logged-in/index.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { useSlack, useWidget, useWidgetId } from '@refocus/sdk';
|
||||
import { SiSlack } from 'react-icons/si';
|
||||
import { Button, View } from '../../base';
|
||||
import { Typography } from '../../typography';
|
||||
import { styled } from 'styled-components';
|
||||
|
||||
const Description = styled(Typography)`
|
||||
text-align: center;
|
||||
`;
|
||||
|
||||
const NotLoggedIn: React.FC = () => {
|
||||
const { login } = useSlack();
|
||||
const type = useWidgetId();
|
||||
const widget = useWidget(type);
|
||||
|
||||
return (
|
||||
<View $p="md" $fc $items="center" $gap="md">
|
||||
<Description>
|
||||
You need to be logged in to Slack to see {widget?.name}
|
||||
</Description>
|
||||
<Button icon={<SiSlack />} onClick={login} title="Login" />
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export { NotLoggedIn };
|
||||
52
packages/ui/src/theme/dark.ts
Normal file
52
packages/ui/src/theme/dark.ts
Normal file
@@ -0,0 +1,52 @@
|
||||
const dark = {
|
||||
colors: {
|
||||
simple: {
|
||||
red: 'red',
|
||||
green: 'green',
|
||||
},
|
||||
bg: {
|
||||
base: 'rgb(25, 26, 35)',
|
||||
base100: 'rgb(38, 39, 54)',
|
||||
hover: 'rgb(38, 39, 54)',
|
||||
highlight: 'rgb(158, 140, 252)',
|
||||
highlight100: 'rgb(158, 140, 252, 0.1)',
|
||||
},
|
||||
text: {
|
||||
base: 'rgb(210, 211, 224)',
|
||||
highlight: '#fff',
|
||||
disabled: 'rgb(210, 211, 224, 0.5)',
|
||||
},
|
||||
},
|
||||
fonts: {
|
||||
body: 'Roboto, sans-serif',
|
||||
},
|
||||
fontSizes: {
|
||||
base: 1,
|
||||
},
|
||||
fontWeights: {
|
||||
base: 400,
|
||||
bold: 700,
|
||||
},
|
||||
lineHeights: {
|
||||
base: 1.5,
|
||||
},
|
||||
radii: {
|
||||
base: 4,
|
||||
md: 8,
|
||||
lg: 16,
|
||||
},
|
||||
shadows: {
|
||||
base: '0 0 0 1px rgba(0, 0, 0, 0.1)',
|
||||
},
|
||||
units: {
|
||||
radii: 'px',
|
||||
space: 'rem',
|
||||
fontSize: 'rem',
|
||||
},
|
||||
space: {
|
||||
sm: 0.5,
|
||||
md: 1,
|
||||
},
|
||||
};
|
||||
|
||||
export { dark };
|
||||
2
packages/ui/src/theme/index.ts
Normal file
2
packages/ui/src/theme/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export { dark } from './dark';
|
||||
export type { Theme } from './theme';
|
||||
43
packages/ui/src/theme/provider.tsx
Normal file
43
packages/ui/src/theme/provider.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import { ThemeProvider, createGlobalStyle } from 'styled-components';
|
||||
import { dark } from '../theme';
|
||||
import { styles } from '../typography';
|
||||
type UIProviderProps = {
|
||||
children: React.ReactNode;
|
||||
};
|
||||
// @import url('https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;600;700&display=swap');
|
||||
const GlobalStyle = createGlobalStyle`
|
||||
|
||||
* {
|
||||
box-sizing: border-box;
|
||||
overflow-wrap: break-word;
|
||||
}
|
||||
|
||||
button {
|
||||
all: unset;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
html, body {
|
||||
height: 100%;
|
||||
overscroll-behavior: none;
|
||||
}
|
||||
|
||||
body {
|
||||
background-color: ${({ theme }) => theme.colors.bg.base};
|
||||
color: ${({ theme }) => theme.colors.text.base};
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
${styles.body}
|
||||
}
|
||||
`;
|
||||
|
||||
const UIProvider: React.FC<UIProviderProps> = ({ children }) => {
|
||||
return (
|
||||
<ThemeProvider theme={dark}>
|
||||
<GlobalStyle />
|
||||
{children}
|
||||
</ThemeProvider>
|
||||
);
|
||||
};
|
||||
|
||||
export { UIProvider };
|
||||
5
packages/ui/src/theme/styled.d.ts
vendored
Normal file
5
packages/ui/src/theme/styled.d.ts
vendored
Normal file
@@ -0,0 +1,5 @@
|
||||
import {} from 'styled-components';
|
||||
import type { Theme } from './theme';
|
||||
declare module 'styled-components' {
|
||||
export interface DefaultTheme extends Theme {} // extends the global DefaultTheme with our ThemeType.
|
||||
}
|
||||
5
packages/ui/src/theme/theme.ts
Normal file
5
packages/ui/src/theme/theme.ts
Normal file
@@ -0,0 +1,5 @@
|
||||
import type { dark } from './dark';
|
||||
|
||||
type Theme = typeof dark;
|
||||
|
||||
export type { Theme };
|
||||
54
packages/ui/src/typography/index.tsx
Normal file
54
packages/ui/src/typography/index.tsx
Normal file
@@ -0,0 +1,54 @@
|
||||
import { styled, css } from 'styled-components';
|
||||
import { View } from '../base/view';
|
||||
|
||||
const styles = {
|
||||
header: css`
|
||||
font-size: 32px;
|
||||
font-weight: bold;
|
||||
`,
|
||||
body: css`
|
||||
font-size: 14px;
|
||||
font-family: ${({ theme }) => theme.fonts.body};
|
||||
font-size: ${({ theme }) => theme.fontSizes.base};
|
||||
line-height: ${({ theme }) => theme.lineHeights.base};
|
||||
font-size: 16px;
|
||||
overflow-wrap: break-word;
|
||||
`,
|
||||
title: css`
|
||||
font-size: 15px;
|
||||
font-weight: bold;
|
||||
`,
|
||||
subtitle: css`
|
||||
font-weight: normal;
|
||||
font-size: 15px;
|
||||
`,
|
||||
dialogTitle: css`
|
||||
font-size: 24px;
|
||||
font-weight: 500;
|
||||
`,
|
||||
tiny: css`
|
||||
font-size: 10px;
|
||||
`,
|
||||
overline: css`
|
||||
font-size: 10px;
|
||||
font-weight: bold;
|
||||
text-transform: uppercase;
|
||||
`,
|
||||
} satisfies Record<string, ReturnType<typeof css>>;
|
||||
|
||||
type TypographyProps = {
|
||||
variant?: keyof typeof styles;
|
||||
};
|
||||
|
||||
const getStyle = (variant: TypographyProps['variant']) => {
|
||||
if (variant && styles[variant]) {
|
||||
return styles[variant];
|
||||
}
|
||||
return styles.body;
|
||||
};
|
||||
|
||||
const Typography = styled(View)<TypographyProps>`
|
||||
${({ variant }) => getStyle(variant)}
|
||||
`;
|
||||
|
||||
export { Typography, styles };
|
||||
33
packages/ui/src/utils/time.ts
Normal file
33
packages/ui/src/utils/time.ts
Normal file
@@ -0,0 +1,33 @@
|
||||
const formatRelativeTime = (date: Date) => {
|
||||
const rtf = new Intl.RelativeTimeFormat('en', { style: 'long' });
|
||||
const diff = Date.now() - date.getTime();
|
||||
const diffInMinutes = Math.floor(diff / 1000 / 60);
|
||||
const diffInHours = Math.floor(diffInMinutes / 60);
|
||||
const diffInDays = Math.floor(diffInHours / 24);
|
||||
const diffInMonths = Math.floor(diffInDays / 30);
|
||||
const diffInYears = Math.floor(diffInMonths / 12);
|
||||
|
||||
if (diffInMinutes < 1) {
|
||||
return 'just now';
|
||||
}
|
||||
|
||||
if (diffInMinutes < 60) {
|
||||
return rtf.format(-diffInMinutes, 'minute');
|
||||
}
|
||||
|
||||
if (diffInHours < 24) {
|
||||
return rtf.format(-diffInHours, 'hour');
|
||||
}
|
||||
|
||||
if (diffInDays < 30) {
|
||||
return rtf.format(-diffInDays, 'day');
|
||||
}
|
||||
|
||||
if (diffInMonths < 12) {
|
||||
return rtf.format(-diffInMonths, 'month');
|
||||
}
|
||||
|
||||
return rtf.format(-diffInYears, 'year');
|
||||
};
|
||||
|
||||
export { formatRelativeTime };
|
||||
16
packages/ui/src/utils/types.ts
Normal file
16
packages/ui/src/utils/types.ts
Normal file
@@ -0,0 +1,16 @@
|
||||
type Expand<T> = T extends infer O ? { [K in keyof O]: O[K] } : never;
|
||||
|
||||
// expands object types recursively
|
||||
type ExpandRecursively<T> = T extends object
|
||||
? T extends infer O
|
||||
? { [K in keyof O]: ExpandRecursively<O[K]> }
|
||||
: never
|
||||
: T;
|
||||
|
||||
type AsyncResponse<T> = T extends () => Promise<infer U> ? U : never;
|
||||
|
||||
type FirstParameter<T> = T extends (arg1: infer U, ...args: any[]) => any
|
||||
? U
|
||||
: never;
|
||||
|
||||
export type { Expand, ExpandRecursively, AsyncResponse, FirstParameter };
|
||||
14
packages/ui/tsconfig.esm.json
Normal file
14
packages/ui/tsconfig.esm.json
Normal file
@@ -0,0 +1,14 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "./dist/esm/types",
|
||||
"outDir": "dist/esm",
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"extends": "@refocus/config/esm",
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"!src/**/*.stories.tsx"
|
||||
]
|
||||
}
|
||||
15
packages/ui/tsconfig.json
Normal file
15
packages/ui/tsconfig.json
Normal file
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"declarationDir": "./dist/cjs/types",
|
||||
"outDir": "dist/cjs",
|
||||
"jsx": "react-jsx",
|
||||
"resolveJsonModule": true,
|
||||
},
|
||||
"extends": "@refocus/config/cjs",
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"!src/**/*.stories.tsx"
|
||||
]
|
||||
}
|
||||
9
packages/ui/vite.config.ts
Normal file
9
packages/ui/vite.config.ts
Normal file
@@ -0,0 +1,9 @@
|
||||
import { defineConfig } from 'vite';
|
||||
import react from '@vitejs/plugin-react-swc';
|
||||
|
||||
const ASSET_URL = process.env.ASSET_URL || '';
|
||||
|
||||
export default defineConfig({
|
||||
base: ASSET_URL ? `${ASSET_URL}/` : './',
|
||||
plugins: [react()],
|
||||
});
|
||||
Reference in New Issue
Block a user