refactor(stories): co-locate stories with components (#2078)
* refactor: move ActionButton component * refactor: move BanUserButton component * refactor: move ChatActionMessage component * refactor: move ChatContainer component * refactor: move AuthModal component * refactor: move BrowserNotifyModal component * refactor: move ChatUserMessage component * refactor: move ChatJoinMessage component * refactor: move ChatTextField component * refactor: move ChatUserBadge component * refactor: move FollowerCollection and SingleFollower components * fix: bad import path * refactor: move FollowModal component * refactor: move Modal component * refactor: move ContentHeader component * refactor: move ChatSystemMessage component * refactor: move Header component * refactor: move Footer component * refactor: move StatusBar component * refactor: move OfflineBanner component * refactor: move OwncastPlayer component * refactor: move IndieAuthModal component * refactor: move SocialLinks component * refactor: move VideoPoster component * refactor: move FollowModal component * refactor: move FediAuthModal.tsx component * refactor: move UserDropdown component * refactor: move ChatSocialMessage component * refactor: move Logo component * refactor: move NotifyReminderPopup component * refactor: move NameChangeModal component * refactor: move FatalErrorStateModal component * refactor: move ChatModeratorNotification component * refactor: move ChatModerationActionMenu and ChatModerationDetailsModal components * refactor: move CustomPageContent component * refactor: move storybook Introduction file * refactor: update storybook story import path * refactor: move storybook preview styles * refactor: move storybook doc pages * refactor: move Color and ImageAsset components * fix: bad import path * fix: bad import path in story file
This commit is contained in:
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ActionButton from '../components/action-buttons/ActionButton';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Action Buttons/Single button',
|
||||
component: ActionButton,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `An **Action Button** or **External Action Button** is a button that is used to trigger either an internal or external action. Many will show a modal, but they can also open a new tab to allow navigating to external pages. They are rendered horizontally within the Action Button Row.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ActionButton>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ActionButton> = args => <ActionButton {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example1 = Template.bind({});
|
||||
Example1.args = {
|
||||
action: {
|
||||
url: 'https://owncast.online/docs',
|
||||
title: 'Documentation',
|
||||
description: 'Owncast Documentation',
|
||||
icon: 'https://owncast.online/images/logo.svg',
|
||||
color: '#5232c8',
|
||||
openExternally: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const Example2 = Template.bind({});
|
||||
Example2.args = {
|
||||
action: {
|
||||
url: 'https://opencollective.com/embed/owncast/donate',
|
||||
title: 'Support Owncast',
|
||||
description: 'Contribute to Owncast',
|
||||
icon: 'https://opencollective.com/static/images/opencollective-icon.svg',
|
||||
color: '#2b4863',
|
||||
openExternally: false,
|
||||
},
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ActionButtonRow from '../components/action-buttons/ActionButtonRow';
|
||||
import ActionButton from '../components/action-buttons/ActionButton';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Action Buttons/Buttons Row',
|
||||
component: ActionButtonRow,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is a horizontal row of buttons that could be statically created by the Owncast application (such as Notify, Follow) or are user-generated external actions (Donate, Learn more, etc).
|
||||
There can be any number of buttons, including zero. They should wrap if needed and handle resizing.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ActionButtonRow>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ActionButtonRow> = args => {
|
||||
const { buttons } = args as any;
|
||||
return <ActionButtonRow>{buttons}</ActionButtonRow>;
|
||||
};
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const actions = [
|
||||
{
|
||||
url: 'https://owncast.online/docs',
|
||||
title: 'Documentation',
|
||||
description: 'Owncast Documentation',
|
||||
icon: 'https://owncast.online/images/logo.svg',
|
||||
color: '#5232c8',
|
||||
openExternally: false,
|
||||
},
|
||||
{
|
||||
url: 'https://opencollective.com/embed/owncast/donate',
|
||||
title: 'Support Owncast',
|
||||
description: 'Contribute to Owncast',
|
||||
icon: 'https://opencollective.com/static/images/opencollective-icon.svg',
|
||||
color: '#2b4863',
|
||||
openExternally: false,
|
||||
},
|
||||
];
|
||||
|
||||
const buttons = actions.map(action => <ActionButton action={action} />);
|
||||
export const Example1 = Template.bind({});
|
||||
Example1.args = {
|
||||
buttons,
|
||||
};
|
||||
@@ -1,26 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import AuthModal from '../components/modals/AuthModal/AuthModal';
|
||||
|
||||
const Example = () => (
|
||||
<div>
|
||||
<AuthModal />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Auth',
|
||||
component: AuthModal,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof AuthModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof AuthModal> = args => (
|
||||
<RecoilRoot>
|
||||
<Example />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,43 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import BrowserNotifyModal from '../components/modals/BrowserNotify/BrowserNotifyModal';
|
||||
import BrowserNotifyModalMock from './assets/mocks/notify-modal.png';
|
||||
|
||||
const Example = () => (
|
||||
<div>
|
||||
<BrowserNotifyModal />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Browser Notifications',
|
||||
component: BrowserNotifyModal,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: BrowserNotifyModalMock,
|
||||
scale: 0.5,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `The notify modal allows an end user to get notified when the stream goes live via [Browser Push Notifications](https://developers.google.com/web/ilt/pwa/introduction-to-push-notifications) It must:
|
||||
|
||||
- Verify the browser supports notifications.
|
||||
- Handle errors that come back from the server.
|
||||
- Have an enabled and disabled state with accurate information about each.
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof BrowserNotifyModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof BrowserNotifyModal> = args => (
|
||||
<RecoilRoot>
|
||||
<Example />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,76 +0,0 @@
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
import { Typography } from 'antd';
|
||||
import UserChatMessage from '../components/chat/ChatUserMessage';
|
||||
import { ChatMessage } from '../interfaces/chat-message.model';
|
||||
|
||||
<Meta title="Owncast/Documentation/Chat" />
|
||||
|
||||
<Typography.Title style={{color: 'var(--primary-color)'}}>Owncast Chat</Typography.Title>
|
||||
|
||||
The Owncast chat is a websocket service that is authenticated with an access token.
|
||||
|
||||
The chat user interface has a handful of different states.
|
||||
|
||||
# App states
|
||||
|
||||
## Offline (stream is not live)
|
||||
|
||||
- The entire chat UI is hidden.
|
||||
|
||||
## Online (stream is live)
|
||||
|
||||
- The chat interface is visible.
|
||||
|
||||
### Online + chat is disconnected
|
||||
|
||||
- Show a loading state within the chat component.
|
||||
- Disable the text input box.
|
||||
|
||||
## Banned from chat
|
||||
|
||||
- The entire chat UI is hidden.
|
||||
|
||||
|
||||
## Stream ended
|
||||
|
||||
- Chat is visible for the next 5 minutes to allow viewers to say goodbye.
|
||||
- Also allows for chat to stay active in case the streamer wants to quickly
|
||||
restart their stream or there's a network blip.
|
||||
|
||||
## Chat Disconnected
|
||||
|
||||
If chat server is not available (websocket disconnects/not available for some reason)
|
||||
then the chat input box should become disabled and placeholder should say chat is not availble.
|
||||
|
||||
# Message types
|
||||
|
||||
## User chat message
|
||||
|
||||
The message that is displayed when a chat user sends a message.
|
||||
|
||||
## System message
|
||||
|
||||
A message sent from the server. Is commonly used for
|
||||
|
||||
- Welcome message.
|
||||
- An external script or integration sending a message on behalf of the server.
|
||||
|
||||
## Action message
|
||||
|
||||
A message saying an action has taken place. Is commonly used for
|
||||
|
||||
- User joined.
|
||||
- User was banned.
|
||||
- User changed name.
|
||||
- Stream is starting.
|
||||
- Stream is ending.
|
||||
- An external script or integration sending an action.
|
||||
|
||||
## Federated action message
|
||||
|
||||
A message stating that somebody on the Fediverse performed an action.
|
||||
It is used for:
|
||||
|
||||
- User "liked" that the steam went live.
|
||||
- User followed the instance.
|
||||
- User shared the instance to their followers.
|
||||
@@ -1,27 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatActionMessage from '../components/chat/ChatAction/ChatActionMessage';
|
||||
import Mock from './assets/mocks/chatmessage-action.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Chat action',
|
||||
component: ChatActionMessage,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is the message design an action takes place, such as a join or a name change.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatActionMessage>;
|
||||
|
||||
const Template: ComponentStory<typeof ChatActionMessage> = args => <ChatActionMessage {...args} />;
|
||||
|
||||
export const Basic = Template.bind({});
|
||||
Basic.args = {
|
||||
body: 'This is a basic action message.',
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -1,42 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatJoinMessage from '../components/chat/ChatJoinMessage/ChatJoinMessage';
|
||||
import Mock from './assets/mocks/chatmessage-action.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Chat Join',
|
||||
component: ChatJoinMessage,
|
||||
argTypes: {
|
||||
userColor: {
|
||||
options: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
control: { type: 'select' },
|
||||
},
|
||||
},
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is the message design an action takes place, such as a join or a name change.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatJoinMessage>;
|
||||
|
||||
const Template: ComponentStory<typeof ChatJoinMessage> = args => <ChatJoinMessage {...args} />;
|
||||
|
||||
export const Regular = Template.bind({});
|
||||
Regular.args = {
|
||||
displayName: 'RandomChatter',
|
||||
isAuthorModerator: false,
|
||||
userColor: 3,
|
||||
};
|
||||
|
||||
export const Moderator = Template.bind({});
|
||||
Moderator.args = {
|
||||
displayName: 'RandomChatter',
|
||||
isAuthorModerator: true,
|
||||
userColor: 2,
|
||||
};
|
||||
@@ -1,97 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatModerationActionMenu from '../components/chat/ChatModerationActionMenu/ChatModerationActionMenu';
|
||||
|
||||
const mocks = {
|
||||
mocks: [
|
||||
{
|
||||
// The "matcher" determines if this
|
||||
// mock should respond to the current
|
||||
// call to fetch().
|
||||
matcher: {
|
||||
name: 'response',
|
||||
url: 'glob:/api/moderation/chat/user/*',
|
||||
},
|
||||
// If the "matcher" matches the current
|
||||
// fetch() call, the fetch response is
|
||||
// built using this "response".
|
||||
response: {
|
||||
status: 200,
|
||||
body: {
|
||||
user: {
|
||||
id: 'hjFPU967R',
|
||||
displayName: 'focused-snyder',
|
||||
displayColor: 2,
|
||||
createdAt: '2022-07-12T13:08:31.406505322-07:00',
|
||||
previousNames: ['focused-snyder'],
|
||||
scopes: ['MODERATOR'],
|
||||
isBot: false,
|
||||
authenticated: false,
|
||||
},
|
||||
connectedClients: [
|
||||
{
|
||||
messageCount: 3,
|
||||
userAgent:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
||||
connectedAt: '2022-07-20T16:45:07.796685618-07:00',
|
||||
geo: 'N/A',
|
||||
},
|
||||
],
|
||||
messages: [
|
||||
{
|
||||
id: 'bQp8UJR4R',
|
||||
timestamp: '2022-07-20T16:53:41.938083228-07:00',
|
||||
user: null,
|
||||
body: 'test message 3',
|
||||
},
|
||||
{
|
||||
id: 'ubK88Jg4R',
|
||||
timestamp: '2022-07-20T16:53:39.675531279-07:00',
|
||||
user: null,
|
||||
body: 'test message 2',
|
||||
},
|
||||
{
|
||||
id: '20v8UJRVR',
|
||||
timestamp: '2022-07-20T16:53:37.551084121-07:00',
|
||||
user: null,
|
||||
body: 'test message 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Moderation menu',
|
||||
component: ChatModerationActionMenu,
|
||||
parameters: {
|
||||
fetchMock: mocks,
|
||||
docs: {
|
||||
description: {
|
||||
component: `This should be a popup that is activated from a user's chat message. It should have actions to:
|
||||
- Remove single message
|
||||
- Ban user completely
|
||||
- Open modal to see user details
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatModerationActionMenu>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatModerationActionMenu> = args => (
|
||||
<RecoilRoot>
|
||||
<ChatModerationActionMenu
|
||||
accessToken="abc123"
|
||||
messageID="xxx"
|
||||
userDisplayName="Fake-User"
|
||||
userID="abc123"
|
||||
/>
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,92 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatModerationDetailsModal from '../components/chat/ChatModerationActionMenu/ChatModerationDetailsModal';
|
||||
|
||||
const mocks = {
|
||||
mocks: [
|
||||
{
|
||||
// The "matcher" determines if this
|
||||
// mock should respond to the current
|
||||
// call to fetch().
|
||||
matcher: {
|
||||
name: 'response',
|
||||
url: 'glob:/api/moderation/chat/user/*',
|
||||
},
|
||||
// If the "matcher" matches the current
|
||||
// fetch() call, the fetch response is
|
||||
// built using this "response".
|
||||
response: {
|
||||
status: 200,
|
||||
body: {
|
||||
user: {
|
||||
id: 'hjFPU967R',
|
||||
displayName: 'focused-snyder',
|
||||
displayColor: 2,
|
||||
createdAt: '2022-07-12T13:08:31.406505322-07:00',
|
||||
previousNames: ['focused-snyder'],
|
||||
scopes: ['MODERATOR'],
|
||||
isBot: false,
|
||||
authenticated: false,
|
||||
},
|
||||
connectedClients: [
|
||||
{
|
||||
messageCount: 3,
|
||||
userAgent:
|
||||
'Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/100.0.4896.127 Safari/537.36',
|
||||
connectedAt: '2022-07-20T16:45:07.796685618-07:00',
|
||||
geo: 'N/A',
|
||||
},
|
||||
],
|
||||
messages: [
|
||||
{
|
||||
id: 'bQp8UJR4R',
|
||||
timestamp: '2022-07-20T16:53:41.938083228-07:00',
|
||||
user: null,
|
||||
body: 'test message 3',
|
||||
},
|
||||
{
|
||||
id: 'ubK88Jg4R',
|
||||
timestamp: '2022-07-20T16:53:39.675531279-07:00',
|
||||
user: null,
|
||||
body: 'test message 2',
|
||||
},
|
||||
{
|
||||
id: '20v8UJRVR',
|
||||
timestamp: '2022-07-20T16:53:37.551084121-07:00',
|
||||
user: null,
|
||||
body: 'test message 1',
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Moderation modal',
|
||||
component: ChatModerationDetailsModal,
|
||||
parameters: {
|
||||
fetchMock: mocks,
|
||||
docs: {
|
||||
description: {
|
||||
component: `This should be a modal that gives the moderator more details about the user such as:
|
||||
- When the user was created
|
||||
- Other names they've used
|
||||
- If they're authenticated, and using what method (IndieAuth, FediAuth)
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatModerationDetailsModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatModerationDetailsModal> = args => (
|
||||
<RecoilRoot>
|
||||
<ChatModerationDetailsModal userId="testuser123" accessToken="fakeaccesstoken4839" />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example = Template.bind({});
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatModeratorNotification from '../components/chat/ChatModeratorNotification/ChatModeratorNotification';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Moderation Role Notification',
|
||||
component: ChatModeratorNotification,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof ChatModeratorNotification>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatModeratorNotification> = (args: object) => (
|
||||
<ChatModeratorNotification {...args} />
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,15 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatSocialMessage from '../components/chat/ChatSocialMessage';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Social-fediverse event',
|
||||
component: ChatSocialMessage,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof ChatSocialMessage>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatSocialMessage> = args => <ChatSocialMessage {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,47 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatSystemMessage from '../components/chat/ChatSystemMessage/ChatSystemMessage';
|
||||
import Mock from './assets/mocks/chatmessage-system.png';
|
||||
import { ChatMessage } from '../interfaces/chat-message.model';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/System',
|
||||
component: ChatSystemMessage,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is the message design used when the server sends a message to chat.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatSystemMessage>;
|
||||
|
||||
const Template: ComponentStory<typeof ChatSystemMessage> = args => <ChatSystemMessage {...args} />;
|
||||
|
||||
const message: ChatMessage = JSON.parse(`{
|
||||
"type": "SYSTEM",
|
||||
"id": "wY-MEXwnR",
|
||||
"timestamp": "2022-04-28T20:30:27.001762726Z",
|
||||
"user": {
|
||||
"id": "h_5GQ6E7R",
|
||||
"displayName": "Cool Server Name",
|
||||
"createdAt": "2022-03-24T03:52:37.966584694Z",
|
||||
"scopes": []
|
||||
},
|
||||
"body": "Test system message from the chat server."}`);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
Basic.args = {
|
||||
message,
|
||||
};
|
||||
|
||||
export const HighlightExample = Template.bind({});
|
||||
HighlightExample.args = {
|
||||
message,
|
||||
highlightString: 'chat',
|
||||
};
|
||||
@@ -1,49 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import ChatTextField from '../components/chat/ChatTextField/ChatTextField';
|
||||
import Mock from './assets/mocks/chatinput-mock.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Input text field',
|
||||
component: ChatTextField,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
- This is a element using \`contentEditable\` in order to support rendering emoji images inline.
|
||||
- Emoji button shows emoji picker.
|
||||
- Should show one line by default, but grow to two lines as needed.
|
||||
- The Send button should be hidden for desktop layouts and be shown for mobile layouts.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatTextField>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof ChatTextField> = args => (
|
||||
<RecoilRoot>
|
||||
<ChatTextField {...args} />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example = Template.bind({});
|
||||
|
||||
export const LongerMessage = Template.bind({});
|
||||
LongerMessage.args = {
|
||||
value:
|
||||
'Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.',
|
||||
};
|
||||
|
||||
LongerMessage.parameters = {
|
||||
docs: {
|
||||
description: {
|
||||
story: 'Should display two lines of text and scroll to display more.',
|
||||
},
|
||||
},
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ChatUserBadge from '../components/chat/ChatUserBadge/ChatUserBadge';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/User Flag',
|
||||
component: ChatUserBadge,
|
||||
argTypes: {
|
||||
userColor: {
|
||||
options: ['0', '1', '2', '3', '4', '5', '6', '7'],
|
||||
control: { type: 'select' },
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof ChatUserBadge>;
|
||||
|
||||
const Template: ComponentStory<typeof ChatUserBadge> = args => <ChatUserBadge {...args} />;
|
||||
|
||||
export const Moderator = Template.bind({});
|
||||
Moderator.args = {
|
||||
badge: 'mod',
|
||||
userColor: '5',
|
||||
};
|
||||
|
||||
export const Authenticated = Template.bind({});
|
||||
Authenticated.args = {
|
||||
badge: 'auth',
|
||||
userColor: '6',
|
||||
};
|
||||
@@ -1,103 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import UserChatMessage from '../components/chat/ChatUserMessage';
|
||||
import { ChatMessage } from '../interfaces/chat-message.model';
|
||||
import Mock from './assets/mocks/chatmessage-user.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Chat/Messages/Standard user',
|
||||
component: UserChatMessage,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
scale: 0.5,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is the standard text message design that is used when a user sends a message in Owncast chat.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof UserChatMessage>;
|
||||
|
||||
const Template: ComponentStory<typeof UserChatMessage> = args => <UserChatMessage {...args} />;
|
||||
|
||||
const standardMessage: ChatMessage = JSON.parse(`{
|
||||
"type": "CHAT",
|
||||
"id": "wY-MEXwnR",
|
||||
"timestamp": "2022-04-28T20:30:27.001762726Z",
|
||||
"user": {
|
||||
"id": "h_5GQ6E7R",
|
||||
"displayName": "EliteMooseTaskForce",
|
||||
"displayColor": 3,
|
||||
"createdAt": "2022-03-24T03:52:37.966584694Z",
|
||||
"previousNames": ["gifted-nobel", "EliteMooseTaskForce"],
|
||||
"nameChangedAt": "2022-04-26T23:56:05.531287897Z",
|
||||
"scopes": []
|
||||
},
|
||||
"body": "Test message from a regular user."}`);
|
||||
|
||||
const moderatorMessage: ChatMessage = JSON.parse(`{
|
||||
"type": "CHAT",
|
||||
"id": "wY-MEXwnR",
|
||||
"timestamp": "2022-04-28T20:30:27.001762726Z",
|
||||
"user": {
|
||||
"id": "h_5GQ6E7R",
|
||||
"displayName": "EliteMooseTaskForce",
|
||||
"displayColor": 2,
|
||||
"createdAt": "2022-03-24T03:52:37.966584694Z",
|
||||
"previousNames": ["gifted-nobel", "EliteMooseTaskForce"],
|
||||
"nameChangedAt": "2022-04-26T23:56:05.531287897Z",
|
||||
"scopes": ["moderator"]
|
||||
},
|
||||
"body": "I am a moderator user."}`);
|
||||
|
||||
const authenticatedUserMessage: ChatMessage = JSON.parse(`{
|
||||
"type": "CHAT",
|
||||
"id": "wY-MEXwnR",
|
||||
"timestamp": "2022-04-28T20:30:27.001762726Z",
|
||||
"user": {
|
||||
"id": "h_5GQ6E7R",
|
||||
"displayName": "EliteMooseTaskForce",
|
||||
"displayColor": 7,
|
||||
"createdAt": "2022-03-24T03:52:37.966584694Z",
|
||||
"previousNames": ["gifted-nobel", "EliteMooseTaskForce"],
|
||||
"nameChangedAt": "2022-04-26T23:56:05.531287897Z",
|
||||
"authenticated": true,
|
||||
"scopes": []
|
||||
},
|
||||
"body": "I am an authenticated user."}`);
|
||||
|
||||
export const WithoutModeratorMenu = Template.bind({});
|
||||
WithoutModeratorMenu.args = {
|
||||
message: standardMessage,
|
||||
showModeratorMenu: false,
|
||||
};
|
||||
|
||||
export const WithModeratorMenu = Template.bind({});
|
||||
WithModeratorMenu.args = {
|
||||
message: standardMessage,
|
||||
showModeratorMenu: true,
|
||||
};
|
||||
|
||||
export const FromModeratorUser = Template.bind({});
|
||||
FromModeratorUser.args = {
|
||||
message: moderatorMessage,
|
||||
showModeratorMenu: false,
|
||||
isAuthorModerator: true,
|
||||
};
|
||||
|
||||
export const FromAuthenticatedUser = Template.bind({});
|
||||
FromAuthenticatedUser.args = {
|
||||
message: authenticatedUserMessage,
|
||||
showModeratorMenu: false,
|
||||
isAuthorAuthenticated: true,
|
||||
};
|
||||
|
||||
export const WithStringHighlighted = Template.bind({});
|
||||
WithStringHighlighted.args = {
|
||||
message: standardMessage,
|
||||
showModeratorMenu: false,
|
||||
highlightString: 'message',
|
||||
};
|
||||
@@ -1,78 +0,0 @@
|
||||
import PropTypes from 'prop-types';
|
||||
|
||||
export function Color(props) {
|
||||
const { color } = props;
|
||||
const resolvedColor = getComputedStyle(document.documentElement).getPropertyValue(`--${color}`);
|
||||
|
||||
const containerStyle = {
|
||||
borderRadius: '20px',
|
||||
width: '12vw',
|
||||
height: '12vw',
|
||||
minWidth: '100px',
|
||||
minHeight: '100px',
|
||||
borderWidth: '1.5px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'lightgray',
|
||||
overflow: 'hidden',
|
||||
margin: '0.3vw',
|
||||
};
|
||||
|
||||
const colorBlockStyle = {
|
||||
display: 'flex',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
textShadow: '0 0 15px black',
|
||||
height: '70%',
|
||||
width: '100%',
|
||||
backgroundColor: resolvedColor,
|
||||
};
|
||||
|
||||
const colorTextStyle = {
|
||||
color: 'white',
|
||||
alignText: 'center',
|
||||
};
|
||||
|
||||
const colorDescriptionStyle = {
|
||||
margin: '5px',
|
||||
color: 'gray',
|
||||
fontSize: '0.95vw',
|
||||
textAlign: 'center' as 'center',
|
||||
lineHeight: 1.0,
|
||||
};
|
||||
|
||||
return (
|
||||
<figure style={containerStyle}>
|
||||
<div style={colorBlockStyle}>
|
||||
<div style={colorTextStyle}>{resolvedColor}</div>
|
||||
</div>
|
||||
<figcaption style={colorDescriptionStyle}>{color}</figcaption>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
|
||||
Color.propTypes = {
|
||||
color: PropTypes.string.isRequired,
|
||||
};
|
||||
|
||||
const rowStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row' as 'row',
|
||||
flexWrap: 'wrap' as 'wrap',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
export function ColorRow(props) {
|
||||
const { colors } = props;
|
||||
|
||||
return (
|
||||
<div style={rowStyle}>
|
||||
{colors.map(color => (
|
||||
<Color key={color} color={color} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
ColorRow.propTypes = {
|
||||
colors: PropTypes.arrayOf(PropTypes.string).isRequired,
|
||||
};
|
||||
@@ -1,111 +0,0 @@
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import { Color, ColorRow } from './Color';
|
||||
|
||||
<Meta title="owncast/Style Guide/Default Theme" />
|
||||
|
||||
# Default theme colors
|
||||
|
||||
These colors are assigned in our [color token](https://github.com/owncast/owncast/tree/webv2/web/style-definitions/tokens/color) files
|
||||
and get reflected here as they change. run `npm run build-styles` to regenerate.
|
||||
|
||||
## Default Theme
|
||||
|
||||
These color names are assigned to specific component variables. They can be overwritten via CSS.
|
||||
|
||||
<ColorRow
|
||||
colors={[
|
||||
'theme-color-palette-0',
|
||||
'theme-color-palette-1',
|
||||
'theme-color-palette-2',
|
||||
'theme-color-palette-3',
|
||||
'theme-color-palette-4',
|
||||
'theme-color-palette-5',
|
||||
'theme-color-palette-6',
|
||||
'theme-color-palette-7',
|
||||
'theme-color-palette-8',
|
||||
'theme-color-palette-9',
|
||||
'theme-color-palette-10',
|
||||
'theme-color-palette-11',
|
||||
'theme-color-palette-12',
|
||||
'theme-color-palette-13',
|
||||
'theme-color-palette-error',
|
||||
'theme-color-palette-warning',
|
||||
'theme-color-background-main',
|
||||
'theme-color-background-header',
|
||||
'theme-color-action',
|
||||
'theme-color-action-hover',
|
||||
'theme-color-action-disabled',
|
||||
]}
|
||||
/>
|
||||
|
||||
## Component Colors
|
||||
|
||||
<ColorRow
|
||||
colors={[
|
||||
'theme-color-components-text-on-light',
|
||||
'theme-color-components-text-on-dark',
|
||||
'theme-color-components-primary-button-background',
|
||||
'theme-color-components-primary-button-background-disabled',
|
||||
'theme-color-components-primary-button-text',
|
||||
'theme-color-components-primary-button-text-disabled',
|
||||
'theme-color-components-primary-button-border',
|
||||
'theme-color-components-secondary-button-background',
|
||||
'theme-color-components-secondary-button-background-disabled',
|
||||
'theme-color-components-secondary-button-text',
|
||||
'theme-color-components-secondary-button-text-disabled',
|
||||
'theme-color-components-secondary-button-border',
|
||||
'theme-color-components-chat-background',
|
||||
'theme-color-components-chat-text',
|
||||
'theme-color-components-modal-header-background',
|
||||
'theme-color-components-modal-header-text',
|
||||
'theme-color-components-modal-content-background',
|
||||
'theme-color-components-modal-content-text',
|
||||
'theme-color-components-menu-background',
|
||||
'theme-color-components-menu-item-text',
|
||||
'theme-color-components-menu-item-bg',
|
||||
'theme-color-components-menu-item-hover-bg',
|
||||
'theme-color-components-menu-item-focus-bg',
|
||||
'theme-color-components-form-field-background',
|
||||
'theme-color-components-form-field-placeholder',
|
||||
'theme-color-components-form-field-text',
|
||||
'theme-color-components-form-field-border',
|
||||
]}
|
||||
/>
|
||||
|
||||
## Default Palette
|
||||
|
||||
These are the core colors for the default, out of the box, Owncast web application theme.
|
||||
They should not be overwritten, instead the theme variables should be overwritten.
|
||||
|
||||
<ColorRow
|
||||
colors={[
|
||||
'color-owncast-palette-0',
|
||||
'color-owncast-palette-1',
|
||||
'color-owncast-palette-2',
|
||||
'color-owncast-palette-3',
|
||||
'color-owncast-palette-4',
|
||||
'color-owncast-palette-5',
|
||||
'color-owncast-palette-6',
|
||||
'color-owncast-palette-7',
|
||||
'color-owncast-palette-9',
|
||||
'color-owncast-palette-10',
|
||||
'color-owncast-palette-11',
|
||||
'color-owncast-palette-12',
|
||||
'color-owncast-palette-13',
|
||||
]}
|
||||
/>
|
||||
|
||||
## User Colors
|
||||
|
||||
<ColorRow
|
||||
colors={[
|
||||
'theme-color-users-0',
|
||||
'theme-color-users-1',
|
||||
'theme-color-users-2',
|
||||
'theme-color-users-3',
|
||||
'theme-color-users-4',
|
||||
'theme-color-users-5',
|
||||
'theme-color-users-6',
|
||||
'theme-color-users-7',
|
||||
]}
|
||||
/>
|
||||
@@ -1,76 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import ContentHeader from '../components/common/ContentHeader/ContentHeader';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Content Header',
|
||||
component: ContentHeader,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof ContentHeader>;
|
||||
|
||||
const Template: ComponentStory<typeof ContentHeader> = args => <ContentHeader {...args} />;
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
name: 'My Awesome Owncast Stream',
|
||||
summary: 'A calvacade of glorious sights and sounds',
|
||||
tags: ['word', 'tag with spaces', 'music'],
|
||||
logo: 'https://watch.owncast.online/logo',
|
||||
links: [
|
||||
{
|
||||
platform: 'github',
|
||||
url: 'https://github.com/owncast/owncast',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/github.svg',
|
||||
},
|
||||
{
|
||||
platform: 'Documentation',
|
||||
url: 'https://owncast.online',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/link.svg',
|
||||
},
|
||||
{
|
||||
platform: 'mastodon',
|
||||
url: 'https://fosstodon.org/users/owncast',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/mastodon.svg',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const LongContent = Template.bind({});
|
||||
LongContent.args = {
|
||||
name: 'My Awesome Owncast Stream, streaming the best of streams and some lorem ipsum too',
|
||||
summary:
|
||||
'A calvacade of glorious sights and sounds. Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.',
|
||||
tags: [
|
||||
'word',
|
||||
'tag with spaces',
|
||||
'music',
|
||||
'more tags',
|
||||
'a bunch',
|
||||
'keep going',
|
||||
'and more',
|
||||
'just a few more',
|
||||
'video games',
|
||||
'things',
|
||||
'stuff',
|
||||
'ok some more',
|
||||
'this should do it',
|
||||
],
|
||||
logo: 'https://watch.owncast.online/logo',
|
||||
links: [
|
||||
{
|
||||
platform: 'github',
|
||||
url: 'https://github.com/owncast/owncast',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/github.svg',
|
||||
},
|
||||
{
|
||||
platform: 'Documentation',
|
||||
url: 'https://owncast.online',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/link.svg',
|
||||
},
|
||||
{
|
||||
platform: 'mastodon',
|
||||
url: 'https://fosstodon.org/users/owncast',
|
||||
icon: 'https://watch.owncast.online/img/platformlogos/mastodon.svg',
|
||||
},
|
||||
],
|
||||
};
|
||||
File diff suppressed because one or more lines are too long
@@ -1,21 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import FatalErrorStateModal from '../components/modals/FatalErrorModal';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Global error state',
|
||||
component: FatalErrorStateModal,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof FatalErrorStateModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof FatalErrorStateModal> = args => (
|
||||
<FatalErrorStateModal {...args} />
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
title: 'Example error title',
|
||||
message: 'Example error message',
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import FediAuthModal from '../components/modals/FediAuthModal';
|
||||
import FediAuthModalMock from './assets/mocks/fediauth-modal.png';
|
||||
|
||||
const Example = () => (
|
||||
<div>
|
||||
<FediAuthModal />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/FediAuth',
|
||||
component: FediAuthModal,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: FediAuthModalMock,
|
||||
scale: 0.5,
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof FediAuthModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof FediAuthModal> = args => <Example />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,38 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import FollowModal from '../components/modals/Follow/FollowModal';
|
||||
import FollowModalMock from './assets/mocks/follow-modal.png';
|
||||
|
||||
const Example = () => (
|
||||
<div>
|
||||
<FollowModal handleClose={null} account="@fake@server.name" name="Fake Owncast Server" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Follow',
|
||||
component: FollowModal,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: FollowModalMock,
|
||||
scale: 0.5,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `The Follow modal allows an end user to type in their Fediverse account information to follow this Owncast instance. It must:
|
||||
|
||||
- Validate the input to make sure it's a valid looking account.
|
||||
- Handle errors that come back from the server.
|
||||
- Perform the redirect to the remote server when the backend response is received.
|
||||
`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof FollowModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof FollowModal> = args => <Example />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,33 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import SingleFollower from '../components/ui/Followers/Follower';
|
||||
import SingleFollowerMock from './assets/mocks/single-follower.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Followers/Single Follower',
|
||||
component: SingleFollower,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: SingleFollowerMock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `Represents a single follower.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof SingleFollower>;
|
||||
|
||||
const Template: ComponentStory<typeof SingleFollower> = args => <SingleFollower {...args} />;
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
follower: {
|
||||
name: 'John Doe',
|
||||
description: 'User',
|
||||
username: '@account@domain.tld',
|
||||
image: 'https://avatars0.githubusercontent.com/u/1234?s=460&v=4',
|
||||
link: 'https://yahoo.com',
|
||||
},
|
||||
};
|
||||
@@ -1,233 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import FollowerCollection from '../components/ui/Followers/FollowersCollection';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Followers/Followers collection',
|
||||
component: FollowerCollection,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof FollowerCollection>;
|
||||
|
||||
const Template: ComponentStory<typeof FollowerCollection> = (args: object) => (
|
||||
<FollowerCollection {...args} />
|
||||
);
|
||||
|
||||
export const NoFollowers = Template.bind({});
|
||||
NoFollowers.args = { followers: [] };
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
followers: [
|
||||
{
|
||||
link: 'https://sun.minuscule.space/users/mardijker',
|
||||
name: 'mardijker',
|
||||
username: 'mardijker@sun.minuscule.space',
|
||||
image:
|
||||
'https://sun.minuscule.space/media/336af7ae5a2bcb508308eddb30b661ee2b2e15004a50795ee3ba0653ab190a93.jpg',
|
||||
timestamp: '2022-04-27T12:12:50Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.online/users/Kallegro',
|
||||
name: '',
|
||||
username: 'Kallegro@mastodon.online',
|
||||
image: '',
|
||||
timestamp: '2022-04-26T15:24:09Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.online/users/kerfuffle',
|
||||
name: 'Kerfuffle',
|
||||
username: 'kerfuffle@mastodon.online',
|
||||
image:
|
||||
'https://files.mastodon.online/accounts/avatars/000/133/698/original/6aa73caa898b2d36.gif',
|
||||
timestamp: '2022-04-25T21:32:41Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.uno/users/informapirata',
|
||||
name: 'informapirata :privacypride:',
|
||||
username: 'informapirata@mastodon.uno',
|
||||
image:
|
||||
'https://cdn.masto.host/mastodonuno/accounts/avatars/000/060/227/original/da4c44c716a339b8.png',
|
||||
timestamp: '2022-04-25T11:38:23Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/Raeanus',
|
||||
name: 'Raeanus',
|
||||
username: 'Raeanus@gamethattune.club',
|
||||
image:
|
||||
'https://gamethattune.club/media/a6e6ccea-34f8-4c2e-b9dc-ad8cca7fafd3/DD14E3BF-1358-4961-A900-42F3495F6BE2.jpeg',
|
||||
timestamp: '2022-04-23T00:46:56Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.ml/users/latte',
|
||||
name: 'Даниил',
|
||||
username: 'latte@mastodon.ml',
|
||||
image:
|
||||
'https://mastodon.ml/system/accounts/avatars/107/837/409/059/601/386/original/c45ec2676489e363.png',
|
||||
timestamp: '2022-04-19T13:06:09Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://wienermobile.rentals/users/jprjr',
|
||||
name: 'Johnny',
|
||||
username: 'jprjr@wienermobile.rentals',
|
||||
image: '',
|
||||
timestamp: '2022-04-14T14:48:11Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/johnny',
|
||||
name: 'John Regan',
|
||||
username: 'johnny@gamethattune.club',
|
||||
image:
|
||||
'https://gamethattune.club/media/3c10cd89-866b-4604-ae40-39387fe17061/profile_large.jpg',
|
||||
timestamp: '2022-04-14T14:42:48Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.social/users/MightyOwlbear',
|
||||
name: 'Haunted Owlbear',
|
||||
username: 'MightyOwlbear@mastodon.social',
|
||||
image:
|
||||
'https://files.mastodon.social/accounts/avatars/107/246/961/007/605/352/original/a86fc3db97a6de04.jpg',
|
||||
timestamp: '2022-04-14T13:33:03Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/thelinkfloyd',
|
||||
name: 'thelinkfloyd',
|
||||
username: 'thelinkfloyd@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-04-05T12:23:32Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/TheBaffler',
|
||||
name: 'TheBaffler',
|
||||
username: 'TheBaffler@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-04-04T19:50:08Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/Gttjessie',
|
||||
name: 'Gttjessie',
|
||||
username: 'Gttjessie@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-03-30T20:18:47Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://cybre.space/users/fractal',
|
||||
name: 'Le fractal',
|
||||
username: 'fractal@cybre.space',
|
||||
image:
|
||||
'https://cybre.ams3.digitaloceanspaces.com/accounts/avatars/000/405/126/original/f1f2832a7bf1a967.png',
|
||||
timestamp: '2022-03-30T19:46:17Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://fosstodon.org/users/jumboshrimp',
|
||||
name: 'alex 👑🦐',
|
||||
username: 'jumboshrimp@fosstodon.org',
|
||||
image: 'https://cdn.fosstodon.org/accounts/avatars/000/320/316/original/de43cda8653ade7f.jpg',
|
||||
timestamp: '2022-03-30T18:09:54Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/nvrslep303',
|
||||
name: 'Tay',
|
||||
username: 'nvrslep303@gamethattune.club',
|
||||
image: 'https://gamethattune.club/media/5cf9bc27-8821-445a-86ce-8aa3704acf2d/pfp.jpg',
|
||||
timestamp: '2022-03-30T15:27:49Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/anKerrigan',
|
||||
name: 'anKerrigan',
|
||||
username: 'anKerrigan@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-03-30T14:47:04Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/jgangsta187',
|
||||
name: 'jgangsta187',
|
||||
username: 'jgangsta187@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-03-30T14:42:52Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/aekre',
|
||||
name: 'aekre',
|
||||
username: 'aekre@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-03-30T14:41:32Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://gamethattune.club/users/mork',
|
||||
name: 'mork',
|
||||
username: 'mork@gamethattune.club',
|
||||
image: '',
|
||||
timestamp: '2022-03-30T14:37:10Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://fosstodon.org/users/owncast',
|
||||
name: 'Owncast',
|
||||
username: 'owncast@fosstodon.org',
|
||||
image:
|
||||
'https://cdn.fosstodon.org/accounts/avatars/107/017/218/425/829/465/original/f98ba4cd61f483ab.png',
|
||||
timestamp: '2022-03-29T21:38:02Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://cybre.space/users/wklew',
|
||||
name: 'wally',
|
||||
username: 'wklew@cybre.space',
|
||||
image:
|
||||
'https://cybre.ams3.digitaloceanspaces.com/accounts/avatars/000/308/727/original/7453e74f3e09b27b.jpg',
|
||||
timestamp: '2022-03-29T18:24:29Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.social/users/nvrslep303',
|
||||
name: 'Tay',
|
||||
username: 'nvrslep303@mastodon.social',
|
||||
image:
|
||||
'https://files.mastodon.social/accounts/avatars/108/041/196/166/285/851/original/fc444dd6096381af.jpg',
|
||||
timestamp: '2022-03-29T18:19:31Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.social/users/morky',
|
||||
name: '',
|
||||
username: 'morky@mastodon.social',
|
||||
image: '',
|
||||
timestamp: '2022-03-29T18:17:59Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://mastodon.social/users/jgangsta187',
|
||||
name: 'John H.',
|
||||
username: 'jgangsta187@mastodon.social',
|
||||
image: '',
|
||||
timestamp: '2022-03-29T18:15:48Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
{
|
||||
link: 'https://fosstodon.org/users/meisam',
|
||||
name: 'Meisam 🇪🇺:archlinux:',
|
||||
username: 'meisam@fosstodon.org',
|
||||
image: 'https://cdn.fosstodon.org/accounts/avatars/000/264/096/original/54b4e6db97206bda.jpg',
|
||||
timestamp: '2022-03-29T18:12:21Z',
|
||||
disabledAt: null,
|
||||
},
|
||||
],
|
||||
};
|
||||
@@ -1,17 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import Footer from '../components/ui/Footer/Footer';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Layout/Footer',
|
||||
component: Footer,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof Footer>;
|
||||
|
||||
const Template: ComponentStory<typeof Footer> = args => <Footer {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
version: 'v1.2.3',
|
||||
};
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import Header from '../components/ui/Header/Header';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Layout/Header',
|
||||
component: Header,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof Header>;
|
||||
|
||||
const Template: ComponentStory<typeof Header> = args => (
|
||||
<RecoilRoot>
|
||||
<Header {...args} />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
export const ChatAvailable = Template.bind({});
|
||||
ChatAvailable.args = {
|
||||
name: 'Example Stream Name',
|
||||
chatAvailable: true,
|
||||
};
|
||||
|
||||
export const ChatNotAvailable = Template.bind({});
|
||||
ChatNotAvailable.args = {
|
||||
name: 'Example Stream Name',
|
||||
chatAvailable: false,
|
||||
};
|
||||
@@ -1,23 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { OwncastLogo } from '../components/common';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Header Logo',
|
||||
component: OwncastLogo,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof OwncastLogo>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof OwncastLogo> = args => <OwncastLogo {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Logo = Template.bind({});
|
||||
Logo.args = {
|
||||
url: '/logo',
|
||||
};
|
||||
|
||||
export const DemoServer = Template.bind({});
|
||||
DemoServer.args = {
|
||||
url: 'https://watch.owncast.online/logo',
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
export function ImageAsset(props: ImageAssetProps) {
|
||||
const { name, src } = props;
|
||||
|
||||
const containerStyle = {
|
||||
borderRadius: '20px',
|
||||
width: '12vw',
|
||||
height: '12vw',
|
||||
minWidth: '100px',
|
||||
minHeight: '100px',
|
||||
borderWidth: '1.5px',
|
||||
borderStyle: 'solid',
|
||||
borderColor: 'lightgray',
|
||||
overflow: 'hidden',
|
||||
margin: '0.3vw',
|
||||
};
|
||||
|
||||
const colorDescriptionStyle = {
|
||||
textAlign: 'center' as 'center',
|
||||
color: 'gray',
|
||||
fontSize: '0.8em',
|
||||
};
|
||||
|
||||
const imageStyle = {
|
||||
width: '100%',
|
||||
height: '80%',
|
||||
backgroundRepeat: 'no-repeat',
|
||||
backgroundSize: 'contain',
|
||||
backgroundPosition: 'center',
|
||||
marginTop: '5px',
|
||||
backgroundImage: `url(${src})`,
|
||||
};
|
||||
|
||||
return (
|
||||
<figure style={containerStyle}>
|
||||
<a href={src} target="_blank" rel="noopener noreferrer">
|
||||
<div style={imageStyle} />
|
||||
<figcaption style={colorDescriptionStyle}>{name}</figcaption>
|
||||
</a>
|
||||
</figure>
|
||||
);
|
||||
}
|
||||
|
||||
interface ImageAssetProps {
|
||||
name: string;
|
||||
src: string;
|
||||
}
|
||||
|
||||
const rowStyle = {
|
||||
display: 'flex',
|
||||
flexDirection: 'row' as 'row',
|
||||
flexWrap: 'wrap' as 'wrap',
|
||||
// justifyContent: 'space-around',
|
||||
alignItems: 'center',
|
||||
};
|
||||
|
||||
export function ImageRow(props: ImageRowProps) {
|
||||
const { images } = props;
|
||||
|
||||
return (
|
||||
<div style={rowStyle}>
|
||||
{images.map(image => (
|
||||
<ImageAsset key={image.src} src={image.src} name={image.name} />
|
||||
))}
|
||||
</div>
|
||||
);
|
||||
}
|
||||
|
||||
interface ImageRowProps {
|
||||
images: ImageAssetProps[];
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
import { Image, ImageRow } from './ImageAsset';
|
||||
|
||||
import Logo from '../assets/images/logo.svg';
|
||||
import FediverseColor from '../assets/images/fediverse-color.png';
|
||||
import FediverseBlack from '../assets/images/fediverse-black.png';
|
||||
import Moderator from '../assets/images/moderator.svg';
|
||||
import IndieAuth from '../assets/images/indieauth.png';
|
||||
import IsBot from '../assets/images/bot.svg';
|
||||
|
||||
<Meta title="owncast/Style Guide/Images+Icons" />
|
||||
|
||||
export const images = [
|
||||
{
|
||||
src: Logo,
|
||||
name: 'Logo',
|
||||
},
|
||||
];
|
||||
|
||||
# Images
|
||||
|
||||
## TODO: Determine the icon style/images for v2 of the web UI.
|
||||
|
||||
|
||||
<ImageRow images={images} />
|
||||
|
||||
## App Icons
|
||||
|
||||
export const icons = [
|
||||
{
|
||||
src: FediverseColor,
|
||||
name: 'Fediverse Color',
|
||||
},
|
||||
{
|
||||
src: FediverseBlack,
|
||||
name: 'Fediverse Black',
|
||||
},
|
||||
{
|
||||
src: Moderator,
|
||||
name: 'Moderator',
|
||||
},
|
||||
{
|
||||
src: IndieAuth,
|
||||
name: 'IndieAuth',
|
||||
},
|
||||
{
|
||||
src: IsBot,
|
||||
name: 'Bot Flag',
|
||||
},
|
||||
];
|
||||
|
||||
<ImageRow images={icons} />
|
||||
@@ -1,28 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import IndieAuthModal from '../components/modals/IndieAuthModal';
|
||||
import Mock from './assets/mocks/indieauth-modal.png';
|
||||
|
||||
const Example = () => (
|
||||
<div>
|
||||
<IndieAuthModal authenticated displayName="fakeChatName" accessToken="fakeaccesstoken" />
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/IndieAuth',
|
||||
component: IndieAuthModal,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
scale: 0.5,
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof IndieAuthModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof IndieAuthModal> = args => <Example />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,67 +0,0 @@
|
||||
import { Meta } from '@storybook/addon-docs';
|
||||
import { Typography } from 'antd';
|
||||
|
||||
<Meta title="Owncast/Readme" />
|
||||
|
||||
<Typography.Title style={{ color: 'var(--primary-color)' }}>Owncast Web UI v2</Typography.Title>
|
||||
|
||||
Owncast is going through a complete rewrite of the web app frontend.
|
||||
|
||||
Visit the [UIv2 milestone](https://github.com/owncast/owncast/milestone/18) on GitHub to see the individual tasks for this project.
|
||||
|
||||
## Quick Links
|
||||
|
||||
- [Redesign project](https://github.com/owncast/owncast/milestone/18)
|
||||
- [Proposed redesign Figma](https://www.figma.com/file/B6ICOn1J3dyYeoZM5kPM2A/Owncast---Review?node-id=643%3A17)
|
||||
- [Currently defined colors](/story/owncast-style-guide-colors--page)
|
||||
- [Owncast Frontend Chat](https://owncast.rocket.chat/group/frontend-dev)
|
||||
|
||||
## Why?
|
||||
|
||||
- Moving to a full React Component workflow allows the project to be more productive and build features faster.
|
||||
- Share code between the web frontend UI and the existing admin.
|
||||
- Address feedback from users.
|
||||
- Better accessibility.
|
||||
- Better mobile experience.
|
||||
- Updated [design](https://www.figma.com/proto/B6ICOn1J3dyYeoZM5kPM2A/Owncast---Review?node-id=643%3A646&scaling=min-zoom&page-id=643%3A17&starting-point-node-id=643%3A44).
|
||||
- Standardize styling across the project by using a single design language and style guide.
|
||||
- Allows more people to contribute to the project if we use popular frameworks.
|
||||
|
||||
## What
|
||||
|
||||
- [Next.js](https://nextjs.org/)
|
||||
- [React](https://reactjs.org/)
|
||||
- [Ant Design](https://ant.design/)
|
||||
|
||||
## Contributing
|
||||
|
||||
1. Find a component that hasn't yet been worked on by looking through the [UIv2 milestone](https://github.com/owncast/owncast/milestone/18)
|
||||
and the sidebar of components to the left.
|
||||
1. See if you can have an example of this functionality in action via the [Owncast Demo Server](https://watch.owncast.online) or [Owncast Nightly Build](https://nightly.owncast.online) so you know how it's supposed to work if it's interactive.
|
||||
1. Visit the `Docs` tab to read any specific documentation that may have been written about how this component works.
|
||||
1. Go to the `Canvas` tab of the component you selected and see if there's a Design attached to it.
|
||||
1. If there is a design, then that's a starting point you can use to start building out the component.
|
||||
1. If there isn't, then visit the [Owncast Demo Server](https://watch.owncast.online), the [Owncast Nightly Build](https://nightly.owncast.online), or the proposed [v2 design](https://www.figma.com/proto/B6ICOn1J3dyYeoZM5kPM2A/Owncast---Review?node-id=643%3A646&scaling=min-zoom&page-id=643%3A17&starting-point-node-id=643%3A44) for some ways to start.
|
||||
1. If no design exists, then you can ask around the Owncast chat for help, for come up with your own ideas!
|
||||
1. No designs are stuck in stone, and we're using this as an opportunity to level up the UI of Owncast, so all ideas are welcome.
|
||||
|
||||
## How?
|
||||
|
||||
This rewrite is a large project, but like anything else, breaking it into pieces and working on one thing at a time will eventually get us to the finish line.
|
||||
And that's what this interface lets us do. On this page we see all the different components still needing to be worked on, and have a place to document the functionality of these pieces.
|
||||
|
||||
## What about the Admin?
|
||||
|
||||
The admin has always been a Next+React+Ant project, so the goal is to touch that as little as possible except where needed to share code and styles.
|
||||
|
||||
## What is this page?
|
||||
|
||||
This is called [_Storybook_](https://storybook.js.org/docs/react/get-started/introduction).
|
||||
Storybook is a tool for UI development. It makes development faster and easier by isolating components.
|
||||
This allows you to work on one component at a time. You can develop entire UIs without needing to start
|
||||
up a complex dev stack, force certain data into your database, or navigate around your application.
|
||||
|
||||
For example a button may have a disabled state that requires a specific scenario to take place in real-world use,
|
||||
but here we you can just toggle the state to verify things are working as expected.
|
||||
|
||||
This means [new components should have a corresponding story added](https://storybook.js.org/docs/react/get-started/whats-a-story) to make it easier to maintain the project.
|
||||
@@ -1,34 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import Modal from '../components/ui/Modal/Modal';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Container',
|
||||
component: Modal,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `This is the popup modal container that all modal content is rendered inside. It can be passed content nodes to render, or a URL to show an iframe.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof Modal>;
|
||||
|
||||
const Template: ComponentStory<typeof Modal> = args => {
|
||||
const { children } = args;
|
||||
return <Modal {...args}>{children}</Modal>;
|
||||
};
|
||||
|
||||
export const Example = Template.bind({});
|
||||
Example.args = {
|
||||
title: 'Modal example with content nodes',
|
||||
visible: true,
|
||||
children: <div>Test 123</div>,
|
||||
};
|
||||
|
||||
export const UrlExample = Template.bind({});
|
||||
UrlExample.args = {
|
||||
title: 'Modal example with URL',
|
||||
visible: true,
|
||||
url: 'https://owncast.online',
|
||||
};
|
||||
@@ -1,20 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import NameChangeModal from '../components/modals/NameChangeModal';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Modals/Name change',
|
||||
component: NameChangeModal,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof NameChangeModal>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof NameChangeModal> = args => (
|
||||
<RecoilRoot>
|
||||
<NameChangeModal />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Basic = Template.bind({});
|
||||
@@ -1,48 +0,0 @@
|
||||
/* eslint-disable no-alert */
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import NotifyReminder from '../components/ui/NotifyReminderPopup/NotifyReminderPopup';
|
||||
import Mock from './assets/mocks/notify-popup.png';
|
||||
|
||||
const Example = args => (
|
||||
<div style={{ margin: '20px', marginTop: '130px' }}>
|
||||
<NotifyReminder {...args}>
|
||||
<button type="button">notify button</button>
|
||||
</NotifyReminder>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Notify Reminder',
|
||||
component: NotifyReminder,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: Mock,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `After visiting the page three times this popup reminding you that you can register for live stream notifications shows up.
|
||||
Clicking it will make the notification modal display. Clicking the "X" will hide the modal and make it never show again.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof NotifyReminder>;
|
||||
|
||||
const Template: ComponentStory<typeof NotifyReminder> = args => <Example {...args} />;
|
||||
|
||||
export const Active = Template.bind({});
|
||||
Active.args = {
|
||||
visible: true,
|
||||
notificationClicked: () => {
|
||||
alert('notification clicked');
|
||||
},
|
||||
notificationClosed: () => {
|
||||
alert('notification closed');
|
||||
},
|
||||
};
|
||||
|
||||
export const InActive = Template.bind({});
|
||||
InActive.args = {
|
||||
visible: false,
|
||||
};
|
||||
@@ -1,35 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import OfflineBanner from '../components/ui/OfflineBanner/OfflineBanner';
|
||||
import OfflineState from './assets/mocks/offline-state.png';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Layout/Offline Banner',
|
||||
component: OfflineBanner,
|
||||
parameters: {
|
||||
design: {
|
||||
type: 'image',
|
||||
url: OfflineState,
|
||||
scale: 0.5,
|
||||
},
|
||||
docs: {
|
||||
description: {
|
||||
component: `When the stream is offline the player should be replaced by this banner that can support custom text and notify actions.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof OfflineBanner>;
|
||||
|
||||
const Template: ComponentStory<typeof OfflineBanner> = args => <OfflineBanner {...args} />;
|
||||
|
||||
export const ExampleDefault = Template.bind({});
|
||||
ExampleDefault.args = {
|
||||
name: 'Cool stream 42',
|
||||
text: 'To get notifications when <server name> is back online you can follow or ask for notifications.',
|
||||
};
|
||||
|
||||
export const ExampleCustom = Template.bind({});
|
||||
ExampleCustom.args = {
|
||||
name: 'Dull stream 31337',
|
||||
text: 'This is some example offline text that a streamer can leave for a visitor of the page.',
|
||||
};
|
||||
@@ -1,39 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import SocialLinks from '../components/ui/SocialLinks/SocialLinks';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/Social links',
|
||||
component: SocialLinks,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof SocialLinks>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof SocialLinks> = args => <SocialLinks {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Populated = Template.bind({});
|
||||
Populated.args = {
|
||||
links: [
|
||||
{
|
||||
platform: 'github',
|
||||
url: 'https://github.com/owncast/owncast',
|
||||
icon: '/img/platformlogos/github.svg',
|
||||
},
|
||||
{
|
||||
platform: 'Documentation',
|
||||
url: 'https://owncast.online',
|
||||
icon: '/img/platformlogos/link.svg',
|
||||
},
|
||||
{
|
||||
platform: 'mastodon',
|
||||
url: 'https://fosstodon.org/users/owncast',
|
||||
icon: '/img/platformlogos/mastodon.svg',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
export const Empty = Template.bind({});
|
||||
Empty.args = {
|
||||
links: [],
|
||||
};
|
||||
@@ -1,25 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { subHours } from 'date-fns';
|
||||
import Statusbar from '../components/ui/Statusbar/Statusbar';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Player/Status bar',
|
||||
component: Statusbar,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof Statusbar>;
|
||||
|
||||
const Template: ComponentStory<typeof Statusbar> = args => <Statusbar {...args} />;
|
||||
|
||||
export const Online = Template.bind({});
|
||||
Online.args = {
|
||||
online: true,
|
||||
viewerCount: 42,
|
||||
lastConnectTime: subHours(new Date(), 3),
|
||||
};
|
||||
|
||||
export const Offline = Template.bind({});
|
||||
Offline.args = {
|
||||
online: false,
|
||||
lastDisconnectTime: subHours(new Date(), 3),
|
||||
};
|
||||
@@ -1,29 +0,0 @@
|
||||
import { Canvas, Meta, Story } from '@storybook/addon-docs';
|
||||
|
||||
<Meta title="owncast/Style Guide/Typography" />
|
||||
|
||||
## Body
|
||||
|
||||
<div style={{ fontSize: '1.2rem', fontFamily: 'var(--theme-text-body-font-family)' }}>
|
||||
The quick brown fox jumps over the lazy dog.
|
||||
</div>
|
||||
|
||||
<Canvas
|
||||
style={{ color: 'var(--theme-text-secondary)', fontFamily: 'var(--theme-text-body-font-family)' }}
|
||||
>
|
||||
{getComputedStyle(document.documentElement).getPropertyValue('--theme-text-body-font-family')}
|
||||
</Canvas>
|
||||
|
||||
## Display
|
||||
|
||||
<div style={{ fontSize: '1.2rem', fontFamily: 'var(--theme-text-display-font-family)' }}>
|
||||
The quick brown fox jumps over the lazy dog.
|
||||
</div>
|
||||
<Canvas
|
||||
style={{
|
||||
color: 'var(--theme-text-secondary)',
|
||||
fontFamily: 'var(--theme-text-display-font-family)',
|
||||
}}
|
||||
>
|
||||
{getComputedStyle(document.documentElement).getPropertyValue('--theme-text-display-font-family')}
|
||||
</Canvas>
|
||||
@@ -1,29 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import { UserDropdown } from '../components/common';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Components/User settings menu',
|
||||
component: UserDropdown,
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof UserDropdown>;
|
||||
|
||||
// This component uses Recoil internally so wrap it in a RecoilRoot.
|
||||
const Example = args => (
|
||||
<RecoilRoot>
|
||||
<UserDropdown {...args} />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
const Template: ComponentStory<typeof UserDropdown> = args => <Example {...args} />;
|
||||
|
||||
export const ChatEnabled = Template.bind({});
|
||||
ChatEnabled.args = {
|
||||
username: 'test-user',
|
||||
};
|
||||
|
||||
export const ChatDisabled = Template.bind({});
|
||||
ChatDisabled.args = {
|
||||
username: 'test-user',
|
||||
};
|
||||
@@ -1,37 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import { RecoilRoot } from 'recoil';
|
||||
import OwncastPlayer from '../components/video/OwncastPlayer';
|
||||
|
||||
const streams = {
|
||||
DemoServer: `https://watch.owncast.online/hls/stream.m3u8`,
|
||||
RetroStrangeTV: `https://live.retrostrange.com/hls/stream.m3u8`,
|
||||
localhost: `http://localhost:8080/hls/stream.m3u8`,
|
||||
};
|
||||
|
||||
export default {
|
||||
title: 'owncast/Player/Player',
|
||||
component: OwncastPlayer,
|
||||
argTypes: {
|
||||
source: {
|
||||
options: Object.keys(streams),
|
||||
mapping: streams,
|
||||
control: {
|
||||
type: 'select',
|
||||
},
|
||||
},
|
||||
},
|
||||
parameters: {},
|
||||
} as ComponentMeta<typeof OwncastPlayer>;
|
||||
|
||||
const Template: ComponentStory<typeof OwncastPlayer> = args => (
|
||||
<RecoilRoot>
|
||||
<OwncastPlayer {...args} />
|
||||
</RecoilRoot>
|
||||
);
|
||||
|
||||
export const LiveDemo = Template.bind({});
|
||||
LiveDemo.args = {
|
||||
online: true,
|
||||
source: 'https://watch.owncast.online/hls/stream.m3u8',
|
||||
};
|
||||
@@ -1,45 +0,0 @@
|
||||
import React from 'react';
|
||||
import { ComponentStory, ComponentMeta } from '@storybook/react';
|
||||
import VideoPoster from '../components/video/VideoPoster';
|
||||
|
||||
export default {
|
||||
title: 'owncast/Player/Video poster',
|
||||
component: VideoPoster,
|
||||
parameters: {
|
||||
docs: {
|
||||
description: {
|
||||
component: `
|
||||
- Sits on top of the video player when playback is not taking place.
|
||||
- Shows the instance logo when the video is offline.
|
||||
- Initial image is the logo when online.
|
||||
- When the stream is online, will transition, via cross-fades, through the thumbnail.
|
||||
- Will be removed when playback starts.`,
|
||||
},
|
||||
},
|
||||
},
|
||||
} as ComponentMeta<typeof VideoPoster>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
const Template: ComponentStory<typeof VideoPoster> = args => <VideoPoster {...args} />;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
export const Example1 = Template.bind({});
|
||||
Example1.args = {
|
||||
initialSrc: 'https://watch.owncast.online/logo',
|
||||
src: 'https://watch.owncast.online/thumbnail.jpg',
|
||||
online: true,
|
||||
};
|
||||
|
||||
export const Example2 = Template.bind({});
|
||||
Example2.args = {
|
||||
initialSrc: 'https://listen.batstationrad.io/logo',
|
||||
src: 'https://listen.batstationrad.io//thumbnail.jpg',
|
||||
online: true,
|
||||
};
|
||||
|
||||
export const Offline = Template.bind({});
|
||||
Offline.args = {
|
||||
initialSrc: 'https://watch.owncast.online/logo',
|
||||
src: 'https://watch.owncast.online/thumbnail.jpg',
|
||||
online: false,
|
||||
};
|
||||
@@ -1,2 +0,0 @@
|
||||
@import '../styles/globals.scss';
|
||||
@import '../styles/ant-overrides.scss';
|
||||
Reference in New Issue
Block a user