mirror of
https://github.com/noodlapp/noodl-docs.git
synced 2026-01-12 15:22:53 +01:00
Initial commit
Co-Authored-By: kotte <14197736+mrtamagotchi@users.noreply.github.com> Co-Authored-By: mikaeltellhed <2311083+mikaeltellhed@users.noreply.github.com> Co-Authored-By: Tore Knudsen <18231882+torekndsn@users.noreply.github.com> Co-Authored-By: Michael Cartner <32543275+michaelcartner@users.noreply.github.com>
This commit is contained in:
105
src/components/cards/ArticleCard/ArticleCard.module.scss
Normal file
105
src/components/cards/ArticleCard/ArticleCard.module.scss
Normal file
@@ -0,0 +1,105 @@
|
||||
@use '../../../css/utils/mixins.scss';
|
||||
|
||||
.Root {
|
||||
display: flex;
|
||||
|
||||
&.is-horisontal {
|
||||
flex-direction: row;
|
||||
}
|
||||
|
||||
&.is-vertical {
|
||||
flex-direction: column;
|
||||
}
|
||||
}
|
||||
|
||||
.ImageContainer {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
position: relative;
|
||||
background-color: #ffffff;
|
||||
|
||||
.Root.is-horisontal & {
|
||||
@include mixins.aspect-ratio(1, 1);
|
||||
width: 156px;
|
||||
}
|
||||
|
||||
.Root.is-vertical & {
|
||||
@include mixins.aspect-ratio(354, 200);
|
||||
}
|
||||
}
|
||||
|
||||
.Image {
|
||||
background-position: center;
|
||||
position: absolute;
|
||||
background-repeat: no-repeat;
|
||||
background-size: cover;
|
||||
top: 0;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
|
||||
.Root.is-vertical &:not(.has-no-padding) {
|
||||
background-size: contain;
|
||||
top: 12px;
|
||||
bottom: 12px;
|
||||
left: 12px;
|
||||
right: 12px;
|
||||
}
|
||||
|
||||
.Root.is-horisontal & {
|
||||
background-size: cover;
|
||||
}
|
||||
}
|
||||
|
||||
.Content {
|
||||
background-color: var(--doc-color-noodl-black-light);
|
||||
flex-grow: 1;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
|
||||
.Root.is-horisontal & {
|
||||
padding: 25px 32px;
|
||||
}
|
||||
|
||||
.Root.is-vertical & {
|
||||
padding: 16px 24px;
|
||||
}
|
||||
}
|
||||
|
||||
.PrimaryAction {
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
margin-right: 24px;
|
||||
border-bottom: none !important;
|
||||
font-family: var(--doc-font-title);
|
||||
|
||||
span {
|
||||
color: var(--doc-color-noodl-orange);
|
||||
transition: color var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
|
||||
&:hover {
|
||||
color: var(--doc-color-noodl-orange-140);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.SecondaryAction {
|
||||
font-weight: 600;
|
||||
cursor: pointer;
|
||||
display: inline-block;
|
||||
border-bottom: none !important;
|
||||
font-family: var(--doc-font-title);
|
||||
|
||||
span {
|
||||
color: var(--doc-color-text);
|
||||
transition: color var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
|
||||
&:hover {
|
||||
color: var(--doc-color-noodl-orange);
|
||||
}
|
||||
}
|
||||
}
|
||||
105
src/components/cards/ArticleCard/ArticleCard.tsx
Normal file
105
src/components/cards/ArticleCard/ArticleCard.tsx
Normal file
@@ -0,0 +1,105 @@
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser'
|
||||
import clsx from 'clsx'
|
||||
import React, { useMemo } from 'react'
|
||||
import { TextSize, Text } from '../../typography/Text/Text'
|
||||
import { Title, TitleSize } from '../../typography/Title/Title'
|
||||
import css from './ArticleCard.module.scss'
|
||||
|
||||
export enum ArticleCardLayout {
|
||||
Horisontal = 'is-horisontal',
|
||||
Vertical = 'is-vertical',
|
||||
}
|
||||
|
||||
interface IActionSlot {
|
||||
label: string
|
||||
onClick?: () => void
|
||||
href?: string
|
||||
}
|
||||
|
||||
export interface ArticleCardProps {
|
||||
layout?: ArticleCardLayout
|
||||
title: string
|
||||
description: string
|
||||
hasNoPaddingInThumb?: boolean
|
||||
primaryAction: IActionSlot
|
||||
secondaryAction?: IActionSlot
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export function ArticleCard({
|
||||
layout = ArticleCardLayout.Vertical,
|
||||
title,
|
||||
description,
|
||||
hasNoPaddingInThumb,
|
||||
primaryAction,
|
||||
secondaryAction,
|
||||
imageUrl,
|
||||
}: ArticleCardProps) {
|
||||
const { siteConfig } = useDocusaurusContext()
|
||||
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
const thumbUrl = useMemo(() => {
|
||||
return !isBrowser
|
||||
? null
|
||||
: location.protocol +
|
||||
'//' +
|
||||
location.host +
|
||||
siteConfig.baseUrl.slice(0, -1) +
|
||||
(imageUrl[0]==='/'?imageUrl:('/'+imageUrl))
|
||||
}, [isBrowser])
|
||||
|
||||
return (
|
||||
<article className={clsx(css['Root'], css[layout])}>
|
||||
<div className={css['ImageContainer']}>
|
||||
{thumbUrl && (
|
||||
<div
|
||||
className={clsx(
|
||||
css['Image'],
|
||||
hasNoPaddingInThumb && css['has-no-padding']
|
||||
)}
|
||||
style={{ backgroundImage: `url(${thumbUrl})` }}
|
||||
/>
|
||||
)}
|
||||
</div>
|
||||
|
||||
<div className={css['Content']}>
|
||||
<div>
|
||||
<header>
|
||||
<Title size={TitleSize.Smaller} headingLevel={1}>
|
||||
{title}
|
||||
</Title>
|
||||
</header>
|
||||
<Text size={TextSize.Small}>{description}</Text>
|
||||
</div>
|
||||
|
||||
<footer className={css['Footer']}>
|
||||
{Boolean(primaryAction) && (
|
||||
<a
|
||||
className={css['PrimaryAction']}
|
||||
href={primaryAction.href}
|
||||
onClick={primaryAction.onClick}
|
||||
>
|
||||
<Text size={TextSize.Small} isSpan>
|
||||
{primaryAction.label}
|
||||
</Text>
|
||||
</a>
|
||||
)}
|
||||
|
||||
{Boolean(secondaryAction) && (
|
||||
<a
|
||||
className={css['SecondaryAction']}
|
||||
href={secondaryAction.href}
|
||||
onClick={secondaryAction.onClick}
|
||||
>
|
||||
<Text size={TextSize.Small} isSpan>
|
||||
{secondaryAction.label}
|
||||
</Text>
|
||||
</a>
|
||||
)}
|
||||
</footer>
|
||||
</div>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
20
src/components/cards/ButtonCard/ButtonCard.module.scss
Normal file
20
src/components/cards/ButtonCard/ButtonCard.module.scss
Normal file
@@ -0,0 +1,20 @@
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
.Root {
|
||||
@include typography.h2;
|
||||
font-size: 26px;
|
||||
line-height: 1.1;
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
padding: 32px 24px 24px;
|
||||
height: 100%;
|
||||
min-height: 180px;
|
||||
border-bottom: none !important;
|
||||
background-color: var(--doc-color-noodl-green);
|
||||
text-align: center;
|
||||
|
||||
&:hover {
|
||||
background-color: var(--doc-color-noodl-green-180);
|
||||
}
|
||||
}
|
||||
19
src/components/cards/ButtonCard/ButtonCard.tsx
Normal file
19
src/components/cards/ButtonCard/ButtonCard.tsx
Normal file
@@ -0,0 +1,19 @@
|
||||
import Link from '@docusaurus/Link'
|
||||
import React from 'react'
|
||||
import css from './ButtonCard.module.scss'
|
||||
|
||||
export interface ButtonCardProps {
|
||||
title: string
|
||||
href?: string
|
||||
onClick?: () => void
|
||||
}
|
||||
|
||||
export function ButtonCard({ title, href, onClick }: ButtonCardProps) {
|
||||
const Tag = href ? Link : 'button'
|
||||
|
||||
return (
|
||||
<Tag className={css['Root']} to={href} onClick={onClick}>
|
||||
{title}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
1
src/components/cards/ButtonCard/index.ts
Normal file
1
src/components/cards/ButtonCard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './ButtonCard'
|
||||
32
src/components/cards/GuideCard/GuideCard.tsx
Normal file
32
src/components/cards/GuideCard/GuideCard.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import React from 'react'
|
||||
import { ArticleCard, ArticleCardLayout } from '../ArticleCard/ArticleCard'
|
||||
|
||||
interface GuideCardProps {
|
||||
title: string
|
||||
description: string
|
||||
href: string
|
||||
imageUrl?: string
|
||||
}
|
||||
|
||||
export function GuideCard({
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
imageUrl,
|
||||
}: GuideCardProps) {
|
||||
const { siteConfig } = useDocusaurusContext()
|
||||
|
||||
return (
|
||||
<ArticleCard
|
||||
layout={ArticleCardLayout.Horisontal}
|
||||
primaryAction={{
|
||||
label: 'Read guide',
|
||||
href: siteConfig.baseUrl.slice(0, -1) + href,
|
||||
}}
|
||||
title={title}
|
||||
description={description}
|
||||
imageUrl={imageUrl}
|
||||
/>
|
||||
)
|
||||
}
|
||||
42
src/components/cards/LinkCard/LinkCard.module.scss
Normal file
42
src/components/cards/LinkCard/LinkCard.module.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
.Root {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
padding: 24px;
|
||||
justify-content: space-between;
|
||||
position: relative;
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
|
||||
&:before {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
bottom: 0;
|
||||
right: 0;
|
||||
pointer-events: none;
|
||||
opacity: 0;
|
||||
transition: opacity var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
background-color: var(--doc-color-noodl-white);
|
||||
}
|
||||
|
||||
&:hover:before {
|
||||
opacity: 0.5;
|
||||
}
|
||||
}
|
||||
|
||||
.IconContainer {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
.Label {
|
||||
@include typography.h3;
|
||||
color: var(--doc-color-noodl-black) !important;
|
||||
max-width: 131px;
|
||||
display: block;
|
||||
position: relative;
|
||||
padding-bottom: 20px;
|
||||
}
|
||||
52
src/components/cards/LinkCard/LinkCard.tsx
Normal file
52
src/components/cards/LinkCard/LinkCard.tsx
Normal file
@@ -0,0 +1,52 @@
|
||||
import Link from '@docusaurus/Link'
|
||||
import React, { useMemo } from 'react'
|
||||
import css from './LinkCard.module.scss'
|
||||
import PlayIcon from '../../../../static/img/icons/play-black.svg'
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser'
|
||||
|
||||
export interface LinkCardProps {
|
||||
href: string
|
||||
colorVariable: string
|
||||
backgroundImage: string
|
||||
label: string
|
||||
playIcon?: boolean
|
||||
}
|
||||
|
||||
export function LinkCard({
|
||||
href,
|
||||
colorVariable,
|
||||
backgroundImage,
|
||||
label,
|
||||
playIcon
|
||||
}: LinkCardProps) {
|
||||
const { siteConfig } = useDocusaurusContext()
|
||||
const isBrowser = useIsBrowser()
|
||||
|
||||
const bgUrl = useMemo(() => {
|
||||
return isBrowser && backgroundImage
|
||||
? location.protocol +
|
||||
'//' +
|
||||
location.host +
|
||||
siteConfig.baseUrl.slice(0, -1) +
|
||||
backgroundImage
|
||||
: null
|
||||
}, [isBrowser])
|
||||
|
||||
return (
|
||||
<Link
|
||||
className={css['Root']}
|
||||
style={{
|
||||
backgroundColor: `var(${colorVariable})`,
|
||||
backgroundImage: bgUrl ? `url(${bgUrl})` : null,
|
||||
}}
|
||||
to={href}
|
||||
>
|
||||
<span className={css['Label']}>{label}</span>
|
||||
|
||||
{(playIcon===undefined || playIcon === true) && (<div className={css['IconContainer']}>
|
||||
<PlayIcon />
|
||||
</div>)}
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
1
src/components/cards/LinkCard/index.ts
Normal file
1
src/components/cards/LinkCard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './LinkCard'
|
||||
41
src/components/cards/ModuleCard/ModuleCard.tsx
Normal file
41
src/components/cards/ModuleCard/ModuleCard.tsx
Normal file
@@ -0,0 +1,41 @@
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import useIsBrowser from '@docusaurus/useIsBrowser'
|
||||
import React from 'react'
|
||||
import { useMemo } from 'react'
|
||||
import {
|
||||
importIntoNoodl,
|
||||
ImportIntoNoodlArgs,
|
||||
} from '../../../utils/importIntoNoodl'
|
||||
import { ArticleCard, ArticleCardLayout } from '../ArticleCard/ArticleCard'
|
||||
|
||||
interface ModuleCardProps {
|
||||
thumbUrl: string
|
||||
title: string
|
||||
description: string
|
||||
readMoreUrl: string
|
||||
importArgs: ImportIntoNoodlArgs
|
||||
}
|
||||
|
||||
export function ModuleCard({
|
||||
thumbUrl,
|
||||
title,
|
||||
description,
|
||||
readMoreUrl,
|
||||
importArgs,
|
||||
}: ModuleCardProps) {
|
||||
const { siteConfig } = useDocusaurusContext()
|
||||
|
||||
return (
|
||||
<ArticleCard
|
||||
title={title}
|
||||
description={description}
|
||||
hasNoPaddingInThumb
|
||||
imageUrl={thumbUrl}
|
||||
layout={ArticleCardLayout.Vertical}
|
||||
primaryAction={{
|
||||
label: 'Read more',
|
||||
href: siteConfig.baseUrl.slice(0, -1) + readMoreUrl,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
51
src/components/cards/NodeCard/NodeCard.module.scss
Normal file
51
src/components/cards/NodeCard/NodeCard.module.scss
Normal file
@@ -0,0 +1,51 @@
|
||||
.Root {
|
||||
display: block;
|
||||
padding: 13px 18px;
|
||||
font-weight: 600;
|
||||
color: var(--doc-color-noodl-white) !important;
|
||||
border: 2px solid transparent !important;
|
||||
position: relative;
|
||||
line-height: 1;
|
||||
height: 100%;
|
||||
font-size: 14px;
|
||||
|
||||
&:after {
|
||||
content: '';
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
background-color: var(--doc-color-noodl-white);
|
||||
opacity: 0;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
&.has-hoverstate:hover {
|
||||
border: 2px solid var(--doc-color-noodl-white) !important;
|
||||
|
||||
&:after {
|
||||
opacity: 0.3;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-visual {
|
||||
background-color: var(--doc-color-visual-node);
|
||||
}
|
||||
|
||||
&.is-data {
|
||||
background-color: var(--doc-color-data-node);
|
||||
}
|
||||
|
||||
&.is-custom {
|
||||
background-color: var(--doc-color-custom-node);
|
||||
}
|
||||
|
||||
&.is-logic {
|
||||
background-color: var(--doc-color-logic-node);
|
||||
}
|
||||
|
||||
&.is-connection {
|
||||
background-color: var(--doc-color-connection-node);
|
||||
}
|
||||
}
|
||||
35
src/components/cards/NodeCard/NodeCard.tsx
Normal file
35
src/components/cards/NodeCard/NodeCard.tsx
Normal file
@@ -0,0 +1,35 @@
|
||||
import Link from '@docusaurus/Link'
|
||||
import clsx from 'clsx'
|
||||
import React from 'react'
|
||||
import css from './NodeCard.module.scss'
|
||||
|
||||
export enum NodeType {
|
||||
Visual = 'is-visual',
|
||||
Data = 'is-data',
|
||||
Custom = 'is-custom',
|
||||
Logic = 'is-logic',
|
||||
Connection = 'is-connection',
|
||||
}
|
||||
|
||||
export interface NodeCardProps {
|
||||
nodeType: NodeType
|
||||
label: string
|
||||
docUrl?: string
|
||||
}
|
||||
|
||||
export function NodeCard({ nodeType, label, docUrl }: NodeCardProps) {
|
||||
const Tag = docUrl ? Link : 'div'
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={clsx(
|
||||
css['Root'],
|
||||
css[nodeType],
|
||||
Boolean(docUrl) && css['has-hoverstate']
|
||||
)}
|
||||
to={docUrl}
|
||||
>
|
||||
{label}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
1
src/components/cards/NodeCard/index.ts
Normal file
1
src/components/cards/NodeCard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './NodeCard'
|
||||
46
src/components/cards/ProjectCard/ProjectCard.tsx
Normal file
46
src/components/cards/ProjectCard/ProjectCard.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import useDocusaurusContext from '@docusaurus/useDocusaurusContext'
|
||||
import React from 'react'
|
||||
import { importIntoNoodl } from '../../../utils/importIntoNoodl'
|
||||
import { ArticleCard, ArticleCardLayout } from '../ArticleCard/ArticleCard'
|
||||
|
||||
interface ProjectCardProps {
|
||||
title: string
|
||||
description: string
|
||||
href: string
|
||||
imageUrl: string
|
||||
project: string
|
||||
backend: string
|
||||
}
|
||||
|
||||
export function ProjectCard({
|
||||
title,
|
||||
description,
|
||||
href,
|
||||
imageUrl,
|
||||
project,
|
||||
backend,
|
||||
}: ProjectCardProps) {
|
||||
const { siteConfig } = useDocusaurusContext()
|
||||
|
||||
return (
|
||||
<ArticleCard
|
||||
layout={ArticleCardLayout.Vertical}
|
||||
title={title}
|
||||
description={description}
|
||||
imageUrl={imageUrl}
|
||||
primaryAction={{
|
||||
label: 'Download project',
|
||||
onClick: () =>
|
||||
importIntoNoodl(project, {
|
||||
name: title,
|
||||
cf: backend,
|
||||
thumb: imageUrl,
|
||||
}),
|
||||
}}
|
||||
secondaryAction={{
|
||||
label: 'Read more',
|
||||
href: siteConfig.baseUrl.slice(0, -1) + href,
|
||||
}}
|
||||
/>
|
||||
)
|
||||
}
|
||||
75
src/components/cards/VideoLinkCard/VideoLinkCard.module.scss
Normal file
75
src/components/cards/VideoLinkCard/VideoLinkCard.module.scss
Normal file
@@ -0,0 +1,75 @@
|
||||
@use '../../../css/utils/mixins.scss';
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
$_hover-background: var(--doc-color-noodl-black-light);
|
||||
|
||||
.Root {
|
||||
display: flex;
|
||||
align-items: stretch;
|
||||
position: relative;
|
||||
outline: 10px solid transparent;
|
||||
transition: outline var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
|
||||
&:hover {
|
||||
outline: 10px solid $_hover-background;
|
||||
}
|
||||
}
|
||||
|
||||
.Thumb {
|
||||
@include mixins.aspect-ratio(560, 315);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
width: calc(50% - 24px);
|
||||
background-size: cover;
|
||||
background-position: center;
|
||||
}
|
||||
|
||||
.Details {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: space-between;
|
||||
width: calc(50% + 24px);
|
||||
padding-left: 24px;
|
||||
transition: background-color var(--ifm-transition-fast)
|
||||
var(--ifm-transition-timing-default);
|
||||
|
||||
.Root:hover & {
|
||||
background-color: $_hover-background;
|
||||
}
|
||||
}
|
||||
|
||||
.Title {
|
||||
@include typography.h4;
|
||||
padding-bottom: 10px;
|
||||
}
|
||||
|
||||
.Description {
|
||||
@include typography.body-small;
|
||||
}
|
||||
|
||||
.VideoLength {
|
||||
font-size: 12px;
|
||||
padding-top: 10px;
|
||||
display: flex;
|
||||
align-items: center;
|
||||
|
||||
svg {
|
||||
width: 18px;
|
||||
height: 18px;
|
||||
}
|
||||
|
||||
span {
|
||||
margin-left: 5px;
|
||||
line-height: 1;
|
||||
}
|
||||
}
|
||||
|
||||
.Link {
|
||||
font-size: 0;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
right: 0;
|
||||
bottom: 0;
|
||||
}
|
||||
47
src/components/cards/VideoLinkCard/VideoLinkCard.tsx
Normal file
47
src/components/cards/VideoLinkCard/VideoLinkCard.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import React from 'react'
|
||||
import css from './VideoLinkCard.module.scss'
|
||||
import PlayIcon from '../../../../static/img/icons/play-white.svg'
|
||||
|
||||
export interface VideoLinkCardProps {
|
||||
videoId: string
|
||||
title: string
|
||||
description: string
|
||||
videoLength: string
|
||||
}
|
||||
|
||||
export function VideoLinkCard({
|
||||
videoId,
|
||||
title,
|
||||
description,
|
||||
videoLength,
|
||||
}: VideoLinkCardProps) {
|
||||
return (
|
||||
<article className={css['Root']}>
|
||||
<div
|
||||
className={css['Thumb']}
|
||||
style={{
|
||||
backgroundImage: `url(http://img.youtube.com/vi/${videoId}/0.jpg)`,
|
||||
}}
|
||||
/>
|
||||
<div className={css['Details']}>
|
||||
<div>
|
||||
<h1 className={css['Title']}>{title}</h1>
|
||||
<p className={css['Description']}>{description}</p>
|
||||
</div>
|
||||
<p className={css['VideoLength']}>
|
||||
<PlayIcon />
|
||||
<span>{videoLength}</span>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<a
|
||||
className={css['Link']}
|
||||
href={`https://www.youtube.com/watch?v=${videoId}`}
|
||||
target="_blank"
|
||||
rel="noopener noreferrer"
|
||||
>
|
||||
Watch video
|
||||
</a>
|
||||
</article>
|
||||
)
|
||||
}
|
||||
1
src/components/cards/VideoLinkCard/index.ts
Normal file
1
src/components/cards/VideoLinkCard/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './VideoLinkCard'
|
||||
15
src/components/common/YouTubeEmbed/YouTubeEmbed.module.scss
Normal file
15
src/components/common/YouTubeEmbed/YouTubeEmbed.module.scss
Normal file
@@ -0,0 +1,15 @@
|
||||
@use '../../../css/utils/mixins.scss';
|
||||
|
||||
.Root {
|
||||
@include mixins.aspect-ratio(560, 315);
|
||||
position: relative;
|
||||
overflow: hidden;
|
||||
|
||||
iframe {
|
||||
left: 0;
|
||||
top: 0;
|
||||
height: 100%;
|
||||
width: 100%;
|
||||
position: absolute;
|
||||
}
|
||||
}
|
||||
22
src/components/common/YouTubeEmbed/YouTubeEmbed.tsx
Normal file
22
src/components/common/YouTubeEmbed/YouTubeEmbed.tsx
Normal file
@@ -0,0 +1,22 @@
|
||||
import React from 'react'
|
||||
import css from './YouTubeEmbed.module.scss'
|
||||
|
||||
export interface YouTubeEmbedProps {
|
||||
videoId: string
|
||||
}
|
||||
|
||||
export function YouTubeEmbed({ videoId }: YouTubeEmbedProps) {
|
||||
return (
|
||||
<div className={css['Root']}>
|
||||
<iframe
|
||||
width="840"
|
||||
height="472"
|
||||
src={`https://www.youtube.com/embed/${videoId}`}
|
||||
title="YouTube video player"
|
||||
frameBorder="0"
|
||||
allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture"
|
||||
allowFullScreen
|
||||
></iframe>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
49
src/components/copytoclipboardbutton.js
Normal file
49
src/components/copytoclipboardbutton.js
Normal file
@@ -0,0 +1,49 @@
|
||||
import React from 'react'
|
||||
|
||||
function fallbackCopyTextToClipboard(text) {
|
||||
var textArea = document.createElement('textarea');
|
||||
textArea.value = text;
|
||||
|
||||
// Avoid scrolling to bottom
|
||||
textArea.style.top = '0';
|
||||
textArea.style.left = '0';
|
||||
textArea.style.position = 'fixed';
|
||||
|
||||
document.body.appendChild(textArea);
|
||||
textArea.focus();
|
||||
textArea.select();
|
||||
|
||||
try {
|
||||
var successful = document.execCommand('copy');
|
||||
var msg = successful ? 'successful' : 'unsuccessful';
|
||||
console.log('Fallback: Copying text command was ' + msg);
|
||||
} catch (err) {
|
||||
console.error('Fallback: Oops, unable to copy', err);
|
||||
}
|
||||
|
||||
document.body.removeChild(textArea);
|
||||
}
|
||||
function copyTextToClipboard(text) {
|
||||
if (!navigator.clipboard) {
|
||||
fallbackCopyTextToClipboard(text);
|
||||
return;
|
||||
}
|
||||
navigator.clipboard.writeText(text).then(
|
||||
function () {
|
||||
console.log('Async: Copying to clipboard was successful!');
|
||||
},
|
||||
function (err) {
|
||||
console.error('Async: Could not copy text: ', err);
|
||||
}
|
||||
);
|
||||
}
|
||||
function copyJsonToClipboard(json) {
|
||||
copyTextToClipboard(JSON.stringify(json));
|
||||
}
|
||||
|
||||
export default function CopyToClipboardButton(props) {
|
||||
return (
|
||||
<button className="ndl-copy-nodes-button" onClick={() => copyJsonToClipboard(props.json)}></button>
|
||||
)
|
||||
}
|
||||
|
||||
29
src/components/importbutton.tsx
Normal file
29
src/components/importbutton.tsx
Normal file
@@ -0,0 +1,29 @@
|
||||
import React from 'react'
|
||||
import { importIntoNoodl } from '../utils/importIntoNoodl'
|
||||
|
||||
interface ImportButtonProps {
|
||||
zip: string
|
||||
name: string
|
||||
thumb: string
|
||||
cf: string
|
||||
}
|
||||
|
||||
export default function ImportButton({
|
||||
zip,
|
||||
name,
|
||||
thumb,
|
||||
cf,
|
||||
}: ImportButtonProps) {
|
||||
return (
|
||||
<button
|
||||
className="ndl-import-button"
|
||||
onClick={() =>
|
||||
importIntoNoodl(zip, {
|
||||
name,
|
||||
thumb,
|
||||
cf,
|
||||
})
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
||||
6
src/components/layout/Container/Container.module.scss
Normal file
6
src/components/layout/Container/Container.module.scss
Normal file
@@ -0,0 +1,6 @@
|
||||
.Root {
|
||||
max-width: var(--ifm-container-width-xl);
|
||||
padding: var(--doc-size-page-top-spacing-l)
|
||||
var(--ifm-navbar-padding-horizontal);
|
||||
margin: 0 auto;
|
||||
}
|
||||
10
src/components/layout/Container/Container.tsx
Normal file
10
src/components/layout/Container/Container.tsx
Normal file
@@ -0,0 +1,10 @@
|
||||
import React, { ReactNode } from 'react'
|
||||
import css from './Container.module.scss'
|
||||
|
||||
export interface ContainerProps {
|
||||
children: ReactNode
|
||||
}
|
||||
|
||||
export function Container({ children }: ContainerProps) {
|
||||
return <div className={css['Root']}>{children}</div>
|
||||
}
|
||||
103
src/components/layout/Grid/Grid.module.scss
Normal file
103
src/components/layout/Grid/Grid.module.scss
Normal file
@@ -0,0 +1,103 @@
|
||||
$_gutter: 24px;
|
||||
|
||||
.Root {
|
||||
display: flex;
|
||||
box-sizing: border-box;
|
||||
flex-wrap: wrap;
|
||||
margin-top: $_gutter * -1;
|
||||
margin-left: $_gutter * -1;
|
||||
|
||||
&.has-equal-height-items {
|
||||
align-items: stretch;
|
||||
}
|
||||
}
|
||||
|
||||
.GridItem {
|
||||
flex-grow: 0;
|
||||
flex-shrink: 0;
|
||||
padding-top: $_gutter;
|
||||
padding-left: $_gutter;
|
||||
pointer-events: none;
|
||||
|
||||
> * {
|
||||
pointer-events: all;
|
||||
}
|
||||
|
||||
.Root.has-equal-height-items & > * {
|
||||
height: 100%;
|
||||
}
|
||||
|
||||
.Root.is-grid-half > & {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 850px) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
.Root.is-grid-thirds > & {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 600px) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media (min-width: 900px) {
|
||||
width: 33.333%;
|
||||
}
|
||||
}
|
||||
|
||||
.Root.is-grid-fifths > & {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 350px) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
@media (min-width: 520px) {
|
||||
width: 33.333%;
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
width: 25%;
|
||||
}
|
||||
|
||||
@media (min-width: 1250px) {
|
||||
width: 20%;
|
||||
}
|
||||
}
|
||||
|
||||
.Root.is-grid-2-1-1 > & {
|
||||
width: 100%;
|
||||
|
||||
@media (min-width: 400px) {
|
||||
&:nth-child(3n-2) {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
&:not(:nth-child(3n-2)) {
|
||||
width: 50%;
|
||||
}
|
||||
}
|
||||
|
||||
@media (min-width: 800px) {
|
||||
&:nth-child(3n-2) {
|
||||
width: 50%;
|
||||
}
|
||||
|
||||
&:not(:nth-child(3n-2)) {
|
||||
width: 25%;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.Root.is-grid-2-3 > & {
|
||||
&:nth-child(odd) {
|
||||
width: 40%;
|
||||
}
|
||||
|
||||
&:nth-child(even) {
|
||||
width: 60%;
|
||||
}
|
||||
}
|
||||
}
|
||||
46
src/components/layout/Grid/Grid.tsx
Normal file
46
src/components/layout/Grid/Grid.tsx
Normal file
@@ -0,0 +1,46 @@
|
||||
import clsx from 'clsx'
|
||||
import React, { ReactNode, useMemo } from 'react'
|
||||
import css from './Grid.module.scss'
|
||||
|
||||
export enum GridLayout {
|
||||
Half = 'is-grid-half',
|
||||
Thirds = 'is-grid-thirds',
|
||||
Fifths = 'is-grid-fifths',
|
||||
Grid_2_1_1 = 'is-grid-2-1-1',
|
||||
Grid_2_3 = 'is-grid-2-3',
|
||||
}
|
||||
|
||||
export interface GridProps {
|
||||
layout: GridLayout
|
||||
children: ReactNode
|
||||
hasEqualHeightItems?: boolean
|
||||
}
|
||||
|
||||
export function Grid({ layout, children, hasEqualHeightItems }: GridProps) {
|
||||
const childArray = useMemo(
|
||||
() => (Array.isArray(children) ? children : [children]),
|
||||
[children]
|
||||
)
|
||||
|
||||
return (
|
||||
<div
|
||||
className={clsx(
|
||||
css['Root'],
|
||||
css[layout],
|
||||
hasEqualHeightItems && css['has-equal-height-items']
|
||||
)}
|
||||
>
|
||||
{/* using i is not the best, but not all items will have ids.
|
||||
if items orders or rendering get all weird or buggy this is the cause */}
|
||||
{childArray.map((child, i) => {
|
||||
if (child === null) return null
|
||||
|
||||
return (
|
||||
<div key={i} className={css['GridItem']}>
|
||||
{child}
|
||||
</div>
|
||||
)
|
||||
})}
|
||||
</div>
|
||||
)
|
||||
}
|
||||
22
src/components/layout/Section/Section.module.scss
Normal file
22
src/components/layout/Section/Section.module.scss
Normal file
@@ -0,0 +1,22 @@
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
.Root {
|
||||
padding-bottom: 120px;
|
||||
}
|
||||
|
||||
.Header {
|
||||
display: flex;
|
||||
justify-content: space-between;
|
||||
align-items: baseline;
|
||||
}
|
||||
|
||||
.Link {
|
||||
@include typography.h3;
|
||||
|
||||
color: var(--doc-color-noodl-white-65) !important; // ajajaj
|
||||
|
||||
&:hover {
|
||||
color: var(--doc-color-noodl-white) !important;
|
||||
border-bottom-color: transparent !important;
|
||||
}
|
||||
}
|
||||
42
src/components/layout/Section/Section.tsx
Normal file
42
src/components/layout/Section/Section.tsx
Normal file
@@ -0,0 +1,42 @@
|
||||
import Link from '@docusaurus/Link'
|
||||
import React, { ReactNode } from 'react'
|
||||
import { Title, TitleProps, TitleSize } from '../../typography/Title/Title'
|
||||
import css from './Section.module.scss'
|
||||
|
||||
export interface SectionProps {
|
||||
title?: TitleProps['children']
|
||||
titleSize?: TitleProps['size']
|
||||
linkLabel?: string | false
|
||||
linkHref?: string
|
||||
children: ReactNode
|
||||
|
||||
hasNoHeader?: boolean
|
||||
}
|
||||
|
||||
export function Section({
|
||||
title,
|
||||
titleSize,
|
||||
linkLabel = 'View all',
|
||||
linkHref,
|
||||
children,
|
||||
|
||||
hasNoHeader,
|
||||
}: SectionProps) {
|
||||
return (
|
||||
<section className={css['Root']}>
|
||||
{!hasNoHeader && (
|
||||
<div className={css['Header']}>
|
||||
<Title size={titleSize}>{title}</Title>
|
||||
|
||||
{Boolean(linkLabel) && (
|
||||
<Link className={css['Link']} href={linkHref}>
|
||||
{linkLabel}
|
||||
</Link>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
|
||||
<div className={css['Content']}>{children}</div>
|
||||
</section>
|
||||
)
|
||||
}
|
||||
1
src/components/layout/Section/index.ts
Normal file
1
src/components/layout/Section/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Section'
|
||||
36
src/components/typography/Text/Text.module.scss
Normal file
36
src/components/typography/Text/Text.module.scss
Normal file
@@ -0,0 +1,36 @@
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
.Root {
|
||||
font-family: var(--doc-font-text);
|
||||
margin-block-start: 0;
|
||||
margin-block-end: 0;
|
||||
max-width: 860px;
|
||||
|
||||
[data-theme='dark'] & {
|
||||
@extend %font-smoothing;
|
||||
}
|
||||
|
||||
&.is-size-default {
|
||||
@include typography.body;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.body-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-size-small {
|
||||
@include typography.body-small;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.body-small-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-inline {
|
||||
display: inline;
|
||||
}
|
||||
|
||||
&.is-centered {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
47
src/components/typography/Text/Text.tsx
Normal file
47
src/components/typography/Text/Text.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import clsx from 'clsx'
|
||||
import React, { CSSProperties, ReactNode } from 'react'
|
||||
import css from './Text.module.scss'
|
||||
|
||||
export enum TextSize {
|
||||
Default = 'default',
|
||||
Small = 'small',
|
||||
}
|
||||
|
||||
export interface TextProps {
|
||||
children?: ReactNode
|
||||
className?: string
|
||||
size?: TextSize
|
||||
style?: CSSProperties
|
||||
|
||||
hasBottomSpacing?: boolean
|
||||
isSpan?: boolean
|
||||
isCentered?: boolean
|
||||
}
|
||||
|
||||
export function Text({
|
||||
children,
|
||||
size = TextSize.Default,
|
||||
style,
|
||||
className,
|
||||
hasBottomSpacing = true,
|
||||
isSpan,
|
||||
isCentered,
|
||||
}: TextProps) {
|
||||
const Tag = isSpan ? 'span' : 'p'
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={clsx(
|
||||
css['Root'],
|
||||
css[`is-size-${size}`],
|
||||
isSpan && css['is-inline'],
|
||||
isCentered && css['is-centered'],
|
||||
hasBottomSpacing && css['has-bottom-spacing'],
|
||||
className
|
||||
)}
|
||||
style={style}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
1
src/components/typography/Text/index.ts
Normal file
1
src/components/typography/Text/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Text'
|
||||
42
src/components/typography/Title/Title.module.scss
Normal file
42
src/components/typography/Title/Title.module.scss
Normal file
@@ -0,0 +1,42 @@
|
||||
@use '../../../css/utils/typography.scss';
|
||||
|
||||
.Root {
|
||||
margin-bottom: 0;
|
||||
margin-top: 0;
|
||||
|
||||
&.is-size-default {
|
||||
@include typography.h2;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.h2-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-size-large {
|
||||
@include typography.h1;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.h1-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-size-small {
|
||||
@include typography.h3;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.h3-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-size-smaller {
|
||||
@include typography.h4;
|
||||
|
||||
&.has-bottom-spacing {
|
||||
@include typography.h4-bottom-spacing;
|
||||
}
|
||||
}
|
||||
|
||||
&.is-centered {
|
||||
text-align: center;
|
||||
}
|
||||
}
|
||||
43
src/components/typography/Title/Title.tsx
Normal file
43
src/components/typography/Title/Title.tsx
Normal file
@@ -0,0 +1,43 @@
|
||||
import clsx from 'clsx'
|
||||
import React, { ReactNode } from 'react'
|
||||
import css from './Title.module.scss'
|
||||
|
||||
export enum TitleSize {
|
||||
Default = 'default',
|
||||
Large = 'large',
|
||||
Small = 'small',
|
||||
Smaller = 'smaller',
|
||||
}
|
||||
|
||||
export interface TitleProps {
|
||||
children?: ReactNode
|
||||
size?: TitleSize
|
||||
headingLevel?: 1 | 2 | 3 | 4 | 5 | 6
|
||||
|
||||
hasBottomSpacing?: boolean
|
||||
isCentered?: boolean
|
||||
}
|
||||
|
||||
export function Title({
|
||||
children,
|
||||
size = TitleSize.Default,
|
||||
headingLevel = 2,
|
||||
hasBottomSpacing = true,
|
||||
isCentered,
|
||||
}: TitleProps) {
|
||||
// hahahaha wtf
|
||||
const Tag: 'h1' | 'h2' | 'h3' | 'h4' | 'h5' | 'h6' = `h${headingLevel}`
|
||||
|
||||
return (
|
||||
<Tag
|
||||
className={clsx(
|
||||
css['Root'],
|
||||
css[`is-size-${size}`],
|
||||
isCentered && css['is-centered'],
|
||||
hasBottomSpacing && css['has-bottom-spacing']
|
||||
)}
|
||||
>
|
||||
{children}
|
||||
</Tag>
|
||||
)
|
||||
}
|
||||
1
src/components/typography/Title/index.ts
Normal file
1
src/components/typography/Title/index.ts
Normal file
@@ -0,0 +1 @@
|
||||
export { default } from './Title'
|
||||
Reference in New Issue
Block a user