11 |
12 | export type ProcessKeys = keyof P extends string ? keyof P : never
13 |
14 | export type Merge = (U extends any ? (k: U) => void : never) extends (
15 | k: infer I
16 | ) => void
17 | ? I
18 | : never
19 |
20 | export type TypeAssertion = Target extends Type
21 | ? Target
22 | : Fallback
23 |
24 | export type StringAssertion = TypeAssertion<
25 | Target,
26 | string,
27 | Fallback
28 | >
29 |
30 | export type NormalizeCombinedIPCs<
31 | T extends Object[],
32 | S extends Merge
33 | > = {
34 | [K in keyof S]: S[K]
35 | }
36 |
37 | export type IPC = {
38 | [Property in keyof Type]: Type[Property] extends (
39 | event: Event,
40 | value: infer V
41 | ) => infer R
42 | ? Type[Property]
43 | : never
44 | }
45 |
46 | export type IPCFactoryProps> = {
47 | main?: {
48 | [K in keyof T['main']]?: (event: IPCMainEvent, args: any) => Promise
49 | }
50 |
51 | renderer?: {
52 | [K in keyof T['renderer']]?: (
53 | event: IPCRendererEvent,
54 | args: any
55 | ) => Promise
56 | }
57 | }
58 |
59 | export type MainHandler = Target extends (
60 | event: IPCMainEvent,
61 | value: infer V
62 | ) => infer R
63 | ? (
64 | handler?: (
65 | event: IPCMainEvent,
66 | handler: { [K in Key extends string ? Key : string]: Target } & {
67 | data: V
68 | },
69 | ...rest: any[]
70 | ) => R
71 | ) => void
72 | : never
73 |
74 | export type RendererHandler = Target extends (
75 | event: IPCRendererEvent,
76 | value: infer V
77 | ) => infer R
78 | ? (
79 | handler?: (
80 | event: IPCRendererEvent,
81 | handler: { [K in Key extends string ? Key : string]: Target } & {
82 | data: V
83 | },
84 | ...rest: any[]
85 | ) => R
86 | ) => void
87 | : never
88 |
89 | export type APIHandlers = {
90 | handle: H
91 | invoke: I
92 | remove: R
93 | }
94 |
95 | export type APIExpose<
96 | H,
97 | I,
98 | R,
99 | S extends (handlers: APIHandlers) => any,
100 | Key,
101 | Append extends any
102 | > = {
103 | apiKey?: String
104 | exposeAll?: boolean
105 | append?: {
106 | [K in keyof Append]: Append[K]
107 | }
108 | override?: (handlers: APIHandlers) => ReturnType
109 | }
110 |
111 | export type API<
112 | Handle,
113 | Invoke,
114 | Remove,
115 | APIConfig extends Record
116 | > = APIExpose<
117 | Handle,
118 | Invoke,
119 | Remove,
120 | (handlers: APIHandlers) => any,
121 | '',
122 | APIConfig['append']
123 | >
124 |
--------------------------------------------------------------------------------
/apps/web/src/components/InstallationBox/index.tsx:
--------------------------------------------------------------------------------
1 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
2 | import { Fragment, useState } from 'react'
3 |
4 | import { queueTimeouts } from 'shared/utils'
5 | import { commands } from 'shared/constants'
6 |
7 | import { Separator, CheckmarkIcon, ClipboardIcon, Spinner } from 'components'
8 |
9 | import { Button, PackageButton, PackageManagerList, ShellBox } from './styles'
10 | import { usePackageManagerSelection } from 'hooks'
11 | import { codeTheme } from 'styles'
12 |
13 | const clipboardStateIcons = {
14 | copying: () => ,
15 | copied: () => ,
16 | default: () => ,
17 | } as const
18 |
19 | type ClipboardStateKeys = keyof typeof clipboardStateIcons
20 | type PackageManagerOptions = keyof typeof commands
21 |
22 | const defaultActivePackageManager = 'yarn'
23 |
24 | export function InstallationBox() {
25 | const [clipboardState, setClipboardState] =
26 | useState('default')
27 |
28 | const { activePackageManager, updateActivePackageManager } =
29 | usePackageManagerSelection(
30 | defaultActivePackageManager
31 | )
32 |
33 | const installCommand = commands[activePackageManager]
34 |
35 | const availablePackagesManagers = Object.keys(
36 | commands
37 | ) as PackageManagerOptions[]
38 |
39 | function copyToClipboard() {
40 | navigator.clipboard
41 | .writeText(installCommand)
42 | .then(() => setClipboardState('copying'))
43 | .then(() =>
44 | queueTimeouts(
45 | {
46 | delay: 1000,
47 | callback: () => setClipboardState('copied'),
48 | },
49 | {
50 | delay: 1000,
51 | callback: () => setClipboardState('default'),
52 | }
53 | )
54 | )
55 | }
56 |
57 | return (
58 | <>
59 |
60 | {availablePackagesManagers.map(
61 | (packageManagerName, index, packageManagers) => {
62 | const isLastItem = index === packageManagers.length - 1
63 |
64 | return (
65 |
66 |
67 |
70 | updateActivePackageManager(packageManagerName)
71 | }
72 | >
73 | {packageManagerName}
74 |
75 |
76 |
77 | {!isLastItem && }
78 |
79 | )
80 | }
81 | )}
82 |
83 |
84 |
85 |
86 | {installCommand}
87 |
88 |
89 |
96 |
97 | >
98 | )
99 | }
100 |
--------------------------------------------------------------------------------
/apps/web/src/components/CodeEditorPresenter/styles.ts:
--------------------------------------------------------------------------------
1 | import { Button } from 'components/Docs/MDXComponents/CodeBox/styles'
2 | import { ExternalLink as _ExternalLink } from 'components/ExternalLink'
3 | import { ButtonStyles } from 'components/Layout'
4 |
5 | import { styled, animations, Mixin } from 'styles'
6 | import { BoxStyles } from '../Layout/Box'
7 |
8 | const SeparatorStyles: Mixin = {
9 | borderBottom: '1px solid $border-primary',
10 | }
11 |
12 | export const CodeContainer = styled('div', {
13 | ...BoxStyles,
14 |
15 | width: '100%',
16 | height: '100%',
17 | maxHeight: 400,
18 | minHeight: 400,
19 | paddingTop: '0.5rem',
20 | overflowY: 'auto',
21 | position: 'relative',
22 | fontSize: '1.1rem',
23 | flexDirection: 'column',
24 | animation: `${animations.reveal} 1s ease-in-out`,
25 |
26 | '.indiana-scroll-container': {
27 | marginRight: '1rem',
28 | display: 'inline-block',
29 | },
30 |
31 | pre: {
32 | width: '100%',
33 | height: '100%',
34 | },
35 | })
36 |
37 | export const MenuContainer = styled('aside', {
38 | ...SeparatorStyles,
39 |
40 | display: 'flex',
41 | gap: '0.5rem',
42 | paddingHorizontal: '0.5rem',
43 | alignItems: 'center',
44 |
45 | '-webkit-transform': 'translate3d(0, 0, 0)',
46 |
47 | ul: {
48 | display: 'inline-flex',
49 | alignItems: 'center',
50 | gap: '0.2rem',
51 | },
52 |
53 | button: {
54 | paddingHorizontal: '0.5rem',
55 | },
56 |
57 | '@bp4': {
58 | gap: '1rem',
59 | paddingHorizontal: '1.4rem',
60 |
61 | ul: {
62 | gap: '1rem',
63 | },
64 | },
65 | })
66 |
67 | export const MenuControls = styled('div', {
68 | display: 'flex',
69 | gap: '0.5rem',
70 | })
71 |
72 | export const CodeContent = styled('div', {
73 | paddingHorizontal: '1rem',
74 | minWidth: '100%',
75 | height: '100vh',
76 | paddingBottom: '1rem',
77 | paddingTop: '0.5rem',
78 | marginTop: 0,
79 | overflow: 'auto',
80 | position: 'relative',
81 |
82 | [`&:hover > ${Button}, &:focus-within > ${Button}`]: {
83 | opacity: 1,
84 | pointerEvents: 'all',
85 | },
86 |
87 | '@bp4': {
88 | marginTop: '0.5rem',
89 | paddingHorizontal: '1.4rem',
90 | },
91 | })
92 |
93 | export const TabButton = styled('button', {
94 | display: 'flex',
95 | alignItems: 'center',
96 | gap: '0.5rem',
97 | justifyContent: 'center',
98 | fontSize: '1rem',
99 | paddingVertical: '0.6rem',
100 | color: '$text-support',
101 | borderBottom: '2px solid transparent',
102 |
103 | svg: {
104 | width: 12,
105 | height: 12,
106 | },
107 |
108 | variants: {
109 | active: {
110 | true: {
111 | color: '$accent-secondary',
112 | borderBottom: '2px solid $accent-primary',
113 | },
114 | },
115 | },
116 |
117 | '@bp4': {
118 | svg: {
119 | width: 15,
120 | height: 15,
121 | },
122 | },
123 | })
124 |
125 | export const IntelliSense = styled('div', {
126 | ...BoxStyles,
127 |
128 | flexDirection: 'column',
129 | left: '3.8rem',
130 | borderRadius: 6,
131 | boxShadow: '4px 4px 15px 5px $colors$shadow-secondary',
132 | minWidth: 200,
133 | minHeight: 80,
134 | position: 'absolute',
135 | zIndex: 2,
136 | overflow: 'hidden',
137 | animation: `${animations.fadeIn} 0.2s ease-in-out`,
138 |
139 | ul: {
140 | display: 'flex',
141 | flexDirection: 'column',
142 | gap: '0.5rem',
143 | },
144 |
145 | li: {
146 | display: 'flex',
147 | alignItems: 'center',
148 | gap: '0.5rem',
149 | color: '$accent-secondary',
150 | paddingHorizontal: '1rem',
151 | paddingVertical: '0.2rem',
152 |
153 | svg: {
154 | width: 16,
155 | height: 16,
156 | },
157 | },
158 | })
159 |
160 | export const CodeSandboxContainer = styled('div', {
161 | ...SeparatorStyles,
162 |
163 | display: 'flex',
164 | justifyContent: 'center',
165 | gap: '0.5rem',
166 | paddingHorizontal: '0.5rem',
167 | paddingBottom: '0.5rem',
168 |
169 | svg: {
170 | width: 15,
171 | height: 15,
172 | },
173 |
174 | '@bp4': {
175 | justifyContent: 'flex-end',
176 | paddingHorizontal: '1rem',
177 | },
178 | })
179 |
180 | export const ExternalLink = styled(_ExternalLink, {
181 | ...ButtonStyles,
182 |
183 | fontSize: '0.9rem',
184 |
185 | '&:hover': {
186 | cursor: 'pointer',
187 | },
188 | })
189 |
--------------------------------------------------------------------------------
/apps/web/src/components/Modals/ExitIntent/styles.ts:
--------------------------------------------------------------------------------
1 | import * as Dialog from '@radix-ui/react-dialog'
2 |
3 | import { getPublicPath } from 'shared/utils'
4 |
5 | import { styled, animations } from 'styles'
6 |
7 | export const Overlay = styled(Dialog.Overlay, {
8 | position: 'fixed',
9 | minWidth: '100vw',
10 | minHeight: '100vh',
11 | inset: 0,
12 | background: 'rgba(0, 0, 0, 0.75)',
13 | zIndex: 100,
14 | })
15 |
16 | export const Container = styled(Dialog.Content, {
17 | display: 'flex',
18 | flexDirection: 'column',
19 | alignItems: 'center',
20 | textAlign: 'center',
21 | width: '100%',
22 | height: '100%',
23 | top: '50%',
24 | left: '50%',
25 | position: 'fixed',
26 | backdropFilter: 'blur(10px)',
27 | transform: 'translate(-50%, -50%)',
28 | overflowX: 'hidden',
29 | zIndex: 9999,
30 |
31 | '@bp4': {
32 | flexDirection: 'row',
33 | justifyContent: 'space-between',
34 | width: '90%',
35 | height: '35.1875rem',
36 | maxWidth: '59.125rem',
37 | borderRadius: 10,
38 | gap: 45.5,
39 | border: '1px solid $colors$shape-tertiary',
40 | boxShadow: '8px 2px 60px -22px $colors$shape-quinary',
41 | overflow: 'hidden',
42 | },
43 | })
44 |
45 | export const Content = styled('div', {
46 | display: 'flex',
47 | flex: 1,
48 | flexDirection: 'column',
49 | width: '100%',
50 | height: '100%',
51 |
52 | '@bp4': {
53 | display: 'flex',
54 | flexDirection: 'row',
55 | width: 'auto',
56 | },
57 | })
58 |
59 | export const ContentGroup = styled('div', {
60 | display: 'flex',
61 | flexDirection: 'column',
62 | alignItems: 'center',
63 | width: '100%',
64 |
65 | textAlign: 'center',
66 |
67 | paddingHorizontal: '1.5rem',
68 | paddingTop: 'calc(4rem + 1.911rem)',
69 | paddingBottom: '3.10375rem',
70 |
71 | gap: 16,
72 |
73 | p: {
74 | fontSize: '0.875rem',
75 | fontWeight: 400,
76 | textAlign: 'center',
77 | lineHeight: '1.4rem',
78 |
79 | color: '$text-support',
80 | },
81 |
82 | a: {
83 | marginTop: 16,
84 | },
85 |
86 | button: {
87 | fontSize: '1rem',
88 | },
89 |
90 | 'button, a': {
91 | width: '100%',
92 | },
93 |
94 | '@bp4': {
95 | flex: 1,
96 | width: '100%',
97 | alignItems: 'flex-start',
98 | textAlign: 'left',
99 | marginLeft: '1.46875rem',
100 |
101 | p: {
102 | textAlign: 'left',
103 | },
104 | },
105 | })
106 |
107 | export const Title = styled(Dialog.Title, {
108 | display: 'inline-block',
109 | maxWidth: '20.1875rem',
110 |
111 | fontSize: '1.5rem',
112 | fontWeight: 700,
113 | lineHeight: '1.875rem',
114 |
115 | color: '$text-title',
116 |
117 | span: {
118 | textTransform: 'capitalize',
119 | },
120 |
121 | strong: {
122 | '&:nth-of-type(odd)': {
123 | color: '$accent-secondary',
124 | },
125 |
126 | '&:nth-of-type(even)': {
127 | color: '$accent-primary',
128 | },
129 | },
130 |
131 | img: {
132 | width: '1.5625rem',
133 | marginLeft: '0.5rem',
134 | },
135 |
136 | '@bp4': {
137 | maxWidth: '33.3125rem',
138 |
139 | fontSize: '2.5rem',
140 | fontWeight: 700,
141 | textAlign: 'left',
142 | lineHeight: '3.125rem',
143 |
144 | img: {
145 | width: '2.5rem',
146 | marginLeft: '0.5rem',
147 | },
148 | },
149 | })
150 |
151 | export const CloseButton = styled(Dialog.Close, {
152 | fontSize: '1.2rem',
153 | lineHeight: 0,
154 | color: '$text-title',
155 | background: 'transparent',
156 | transform: 'scaleX(1.4)',
157 | top: '1.5rem',
158 | right: '1.5rem',
159 | border: 0,
160 | cursor: 'pointer',
161 | position: 'absolute',
162 | transition: 'color 0.2s ease-in-out',
163 |
164 | '&:hover, &:focus-within': {
165 | color: '$accent-primary',
166 | },
167 | })
168 |
169 | export const Tag = styled('h2', {
170 | fontSize: '0.875rem',
171 | fontWeight: 700,
172 | lineHeight: '1.4rem',
173 | textTransform: 'uppercase',
174 |
175 | color: '$accent-primary',
176 |
177 | padding: '4px 8px',
178 |
179 | borderRadius: 3,
180 |
181 | variants: {
182 | type: {
183 | outlined: {
184 | border: '1px solid $accent-primary',
185 | },
186 | },
187 | },
188 |
189 | defaultVariants: {
190 | type: 'outlined',
191 | },
192 | })
193 |
194 | export const Image = styled('div', {
195 | display: 'flex',
196 | width: '100%',
197 | height: '3.125rem',
198 | zIndex: -1,
199 |
200 | backgroundImage: `url("${getPublicPath('/abstract-background.jpg')}")`,
201 | backgroundPosition: 'center center',
202 | willChange: 'background',
203 | '-webkit-transform': 'translate3d(0)',
204 | animation: `${animations.backgroundCover} 40s linear infinite alternate`,
205 |
206 | '@bp4': {
207 | maxWidth: '20rem',
208 | minHeight: '100%',
209 | },
210 | })
211 |
--------------------------------------------------------------------------------
/apps/web/src/components/CodeEditorPresenter/index.tsx:
--------------------------------------------------------------------------------
1 | import { useWindupString, WindupChildren, textFromChildren } from 'windups'
2 | import { Prism as SyntaxHighlighter } from 'react-syntax-highlighter'
3 | import React, { useCallback, useEffect, useRef, useState } from 'react'
4 | import ScrollableContainer from 'react-indiana-drag-scroll'
5 |
6 | import { codes, tabs } from 'shared/constants'
7 |
8 | import { Button, CodeSandboxIcon, FileIcon, Separator } from 'components'
9 |
10 | import { RendererIntelliSenseTab } from './IntelliSenses/renderer'
11 | import { PreloadIntelliSenseTab } from './IntelliSenses/preload'
12 | import { IpcsIntelliSenseTab } from './IntelliSenses/ipcs'
13 | import { MainIntelliSenseTab } from './IntelliSenses/main'
14 |
15 | import {
16 | TabButton,
17 | CodeContent,
18 | MenuControls,
19 | ExternalLink,
20 | MenuContainer,
21 | CodeContainer,
22 | CodeSandboxContainer,
23 | } from './styles'
24 |
25 | import { CodeBox } from 'components/Docs/MDXComponents/CodeBox'
26 |
27 | const tabsData = [
28 | {
29 | code: codes.ipcs,
30 | IntelliSense: IpcsIntelliSenseTab,
31 | },
32 | {
33 | code: codes.main,
34 | IntelliSense: MainIntelliSenseTab,
35 | },
36 | {
37 | code: codes.preload,
38 | IntelliSense: PreloadIntelliSenseTab,
39 | },
40 | {
41 | code: codes.renderer,
42 | IntelliSense: RendererIntelliSenseTab,
43 | },
44 | ]
45 |
46 | export function CodeEditorPresenterSection() {
47 | const [activeTab, setActiveTab] = useState(0)
48 | const [tabsAlreadyVisited, setTabsAlreadyVisited] = useState([])
49 |
50 | const Tab = tabsData[activeTab]
51 | const isActiveTabAlreadyVisited = tabsAlreadyVisited.includes(activeTab)
52 |
53 | const [typewrittenText, controls] = useWindupString(Tab.code.finalState, {
54 | pace: () => 1,
55 |
56 | onFinished() {
57 | if (!isActiveTabAlreadyVisited) {
58 | setTabsAlreadyVisited((prevState) => [...prevState, activeTab])
59 | }
60 |
61 | setTimeout(
62 | () => {
63 | containerRef.current?.scrollTo(0, 0)
64 | },
65 | isActiveTabAlreadyVisited ? 0 : 700
66 | )
67 | },
68 | })
69 |
70 | const containerRef = useRef(null)
71 |
72 | useEffect(() => {
73 | const container = containerRef.current
74 |
75 | if (isActiveTabAlreadyVisited || !container) {
76 | return controls.skip()
77 | }
78 |
79 | container.scrollTo(0, container.scrollHeight)
80 | }, [typewrittenText])
81 |
82 | const handleRemoveTabFromVisitedList = useCallback(() => {
83 | setTabsAlreadyVisited((prevState) =>
84 | prevState.filter((item) => item !== activeTab)
85 | )
86 | }, [activeTab])
87 |
88 | return (
89 |
90 |
91 |
95 |
96 | Simple
97 |
98 |
99 |
103 |
104 | Advanced
105 |
106 |
107 |
108 |
109 |
110 |
117 |
118 |
128 |
129 |
130 |
131 |
132 | {tabs.fileNames.map((fileName, index) => (
133 |
134 | setActiveTab(index)}
136 | active={Boolean(activeTab === index)}
137 | >
138 | {fileName}
139 |
140 |
141 | {index !== tabsData.length - 1 && }
142 |
143 | ))}
144 |
145 |
146 |
147 |
148 |
149 | {Tab.IntelliSense && (
150 |
155 | )}
156 |
157 |
158 | {textFromChildren({typewrittenText})}
159 |
160 |
161 |
162 | )
163 | }
164 |
--------------------------------------------------------------------------------
/apps/web/src/styles/lib/react-syntax-highlighter/theme.ts:
--------------------------------------------------------------------------------
1 | export function createCodeTheme({
2 | primary,
3 | secondary,
4 | }: {
5 | primary: string
6 | secondary: string
7 | }) {
8 | return {
9 | 'code[class*="language-"]': {
10 | textAlign: 'left',
11 | graySpace: 'pre',
12 | wordSpacing: 'normal',
13 | wordBreak: 'normal',
14 | wordWrap: 'normal',
15 | color: '#eee',
16 | fontFamily: 'Roboto Mono, monospace',
17 | fontSize: '1em',
18 | lineHeight: '1.5em',
19 | MozTabSize: '4',
20 | OTabSize: '4',
21 | tabSize: '4',
22 | WebkitHyphens: 'none',
23 | MozHyphens: 'none',
24 | msHyphens: 'none',
25 | hyphens: 'none',
26 | },
27 |
28 | 'pre[class*="language-"]': {
29 | textAlign: 'left',
30 | graySpace: 'pre',
31 | wordSpacing: 'normal',
32 | wordBreak: 'normal',
33 | wordWrap: 'normal',
34 | color: '#eee',
35 | fontFamily: 'Roboto Mono, monospace',
36 | fontSize: '1em',
37 | lineHeight: '1.5em',
38 | MozTabSize: '4',
39 | OTabSize: '4',
40 | tabSize: '4',
41 | WebkitHyphens: 'none',
42 | MozHyphens: 'none',
43 | msHyphens: 'none',
44 | hyphens: 'none',
45 | overflow: 'auto',
46 | position: 'relative',
47 | // margin: '0.5em 0',
48 | // padding: '1.25em 1em',
49 | },
50 |
51 | 'code[class*="language-"]::-moz-selection': {
52 | background: '#363636',
53 | },
54 |
55 | 'pre[class*="language-"]::-moz-selection': {
56 | background: '#363636',
57 | },
58 |
59 | 'code[class*="language-"] ::-moz-selection': {
60 | background: '#363636',
61 | },
62 |
63 | 'pre[class*="language-"] ::-moz-selection': {
64 | background: '#363636',
65 | },
66 |
67 | 'code[class*="language-"]::selection': {
68 | background: '#363636',
69 | },
70 |
71 | 'pre[class*="language-"]::selection': {
72 | background: '#363636',
73 | },
74 |
75 | 'code[class*="language-"] ::selection': {
76 | background: '#363636',
77 | },
78 |
79 | 'pre[class*="language-"] ::selection': {
80 | background: '#363636',
81 | },
82 |
83 | ':not(pre) > code[class*="language-"]': {
84 | graySpace: 'normal',
85 | borderRadius: '0.2em',
86 | padding: '0.1em',
87 | },
88 |
89 | '.language-css > code': {
90 | color: secondary,
91 | },
92 |
93 | '.language-sass > code': {
94 | color: secondary,
95 | },
96 |
97 | '.language-scss > code': {
98 | color: secondary,
99 | },
100 |
101 | '[class*="language-"] .namespace': {
102 | Opacity: '0.7',
103 | },
104 |
105 | atrule: {
106 | color: secondary,
107 | },
108 |
109 | 'attr-name': {
110 | color: '#64ffa4',
111 | },
112 |
113 | 'attr-value': {
114 | color: secondary,
115 | },
116 |
117 | attribute: {
118 | color: secondary,
119 | },
120 |
121 | boolean: {
122 | color: secondary,
123 | },
124 |
125 | builtin: {
126 | color: '#ffcb6b',
127 | },
128 |
129 | cdata: {
130 | color: primary,
131 | },
132 |
133 | char: {
134 | color: primary,
135 | },
136 |
137 | class: {
138 | color: '#ffcb6b',
139 | },
140 |
141 | 'class-name': {
142 | color: primary,
143 | },
144 |
145 | comment: {
146 | color: '#616161',
147 | },
148 |
149 | constant: {
150 | color: secondary,
151 | },
152 |
153 | deleted: {
154 | color: secondary,
155 | },
156 |
157 | doctype: {
158 | color: '#616161',
159 | },
160 |
161 | entity: {
162 | color: secondary,
163 | },
164 |
165 | function: {
166 | color: primary,
167 | },
168 |
169 | hexcode: {
170 | color: '#f2ff00',
171 | },
172 |
173 | id: {
174 | color: secondary,
175 | fontWeight: 'bold',
176 | },
177 |
178 | important: {
179 | color: secondary,
180 | fontWeight: 'bold',
181 | },
182 |
183 | inserted: {
184 | color: primary,
185 | },
186 |
187 | keyword: {
188 | color: secondary,
189 | },
190 |
191 | number: {
192 | color: secondary,
193 | },
194 |
195 | operator: {
196 | color: 'gray',
197 | },
198 |
199 | prolog: {
200 | color: '#616161',
201 | },
202 |
203 | property: {
204 | color: primary,
205 | },
206 |
207 | 'pseudo-class': {
208 | color: secondary,
209 | },
210 |
211 | 'pseudo-element': {
212 | color: secondary,
213 | },
214 |
215 | punctuation: {
216 | color: 'gray',
217 | },
218 |
219 | regex: {
220 | color: '#f2ff00',
221 | },
222 |
223 | selector: {
224 | color: secondary,
225 | },
226 |
227 | string: {
228 | color: '#77bddf',
229 | },
230 |
231 | symbol: {
232 | color: secondary,
233 | },
234 |
235 | tag: {
236 | color: secondary,
237 | },
238 |
239 | unit: {
240 | color: secondary,
241 | },
242 |
243 | url: {
244 | color: secondary,
245 | },
246 |
247 | variable: {
248 | color: secondary,
249 | },
250 | } as { [key: string]: React.CSSProperties }
251 | }
252 |
--------------------------------------------------------------------------------
/packages/interprocess/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | interprocess: 💬 A scalable and type-safe Electron IPC management tool with enhanced DX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | > Electron IPC is good, but difficult to maintain and scale, either because of the numerous channels you have to remember, or because of the inconsistent API between processes and the absence of inferred types of your channels and handlers. These are some of the things that interprocess comes to solve!
29 |
30 | # 💬 Features
31 | - 🚀 Best-in-class DX with a fully-typed API
32 | - 🧠 Enchanced and consistent API
33 | - 🔥 Type-safe and scalable
34 | - 🪄 Code splitting support
35 | - 🕸️ All edges connected (APIs to handle all processes)
36 | - 💖 `invoke` and `handle` methods in both processes with the same expected behavior
37 |
38 | # Requirements
39 | - Node.js >= 14.0.0
40 | - Electron >= 12.0.0
41 | - TypeScript >= 4.8.0
42 |
43 | # 💬 Installation
44 | In your terminal, run:
45 | ```bash
46 | yarn add interprocess
47 |
48 | # OR
49 |
50 | npm i interprocess
51 | ```
52 |
53 | # 💬 Usage
54 |
55 | Let's build something simple that can show you some of the interprocess's power!
56 |
57 | First, create the following folders at `src`:
58 |
59 | - `shared/ipcs` (this folder structure is optional)
60 |
61 | Then, create a file named as `index.ts` in the `ipcs` folder with the following content:
62 |
63 | ```ts
64 | import { createInterprocess } from 'interprocess'
65 |
66 | export const { ipcMain, ipcRenderer, exposeApiToGlobalWindow } =
67 | createInterprocess({
68 | main: {
69 | async getPing(_, data: 'ping') {
70 | const message = `from renderer: ${data} on main process`
71 |
72 | console.log(message)
73 |
74 | return message
75 | },
76 | },
77 |
78 | renderer: {
79 | async getPong(_, data: 'pong') {
80 | const message = `from main: ${data} on renderer process`
81 |
82 | console.log(message)
83 |
84 | return message
85 | },
86 | },
87 | })
88 | ```
89 |
90 | On the **main process**:
91 |
92 | > :warning: Don't forget to add `sandbox: false` to the BrowserWindow because it's required to load the preload script properly!
93 |
94 | ```ts
95 | import { BrowserWindow, app } from 'electron'
96 |
97 | import { ipcMain } from 'shared/ipcs'
98 |
99 | const { handle, invoke } = ipcMain
100 |
101 | app.whenReady().then(() => {
102 | const mainWindow = new BrowserWindow({
103 | webPreferences: {
104 | preload: path.join(__dirname, '../preload/index.js'),
105 | sandbox: false, // sandbox must be false
106 | },
107 | })
108 |
109 | handle.getPing()
110 |
111 | mainWindow.webContents.on('dom-ready', () => {
112 | invoke.getPong(mainWindow, 'pong')
113 | })
114 | })
115 | ```
116 |
117 | In the **preload script**:
118 |
119 | ```ts
120 | import { exposeApiToGlobalWindow } from 'shared/ipcs'
121 |
122 | const { key, api } = exposeApiToGlobalWindow({
123 | exposeAll: true, // expose handlers, invokers and removers
124 | })
125 |
126 | declare global {
127 | interface Window {
128 | [key]: typeof api
129 | }
130 | }
131 | ```
132 |
133 | On the **renderer process**:
134 |
135 | ```ts
136 | const { invoke, handle } = window.api
137 |
138 | invoke.getPing('ping')
139 | handle.getPong()
140 | ```
141 | This is a simple way to work with interprocess, but there's a lot of more cool features you can take advantage, like overrides, code splitting, invoker's response (for renderer and main process 🎉) and more. See the [Knowledge section for more](#-knowledge)
142 |
143 | # 💬 Knowledge
144 | - [Docs](https://interprocess.daltonmenezes.com/docs/getting-started/overview)
145 |
146 | - Examples
147 | - [Executable](https://github.com/daltonmenezes/interprocess/tree/main/apps/desktop)
148 | - [CodeSandbox (simple)](https://codesandbox.io/s/simple-607b6h?file=/src/ipcs.ts)
149 | - [CodeSandbox (advanced)](https://codesandbox.io/s/advanced-4qh0xb?file=/src/ipcs/index.ts)
150 |
151 |
152 | # 💬 Contributing
153 | > **Note**: contributions are always welcome, but always **ask first**, — please — before work on a PR.
154 |
155 | That said, there's a bunch of ways you can contribute to this project, like by:
156 |
157 | - :beetle: Reporting a bug
158 | - :page_facing_up: Improving the docs
159 | - :rotating_light: Sharing this project and recommending it to your friends
160 | - :dollar: Supporting this project on GitHub Sponsors or Patreon
161 | - :bug: Funding an issue on IssueHunt
162 | - :star2: Giving a star on this repository
163 |
164 | # License
165 |
166 | [MIT © Dalton Menezes](https://github.com/daltonmenezes/interprocess/blob/main/LICENSE)
167 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 |
7 | interprocess: 💬 A scalable and type-safe Electron IPC management tool with enhanced DX
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 | > Electron IPC is good, but difficult to maintain and scale, either because of the numerous channels you have to remember, or because of the inconsistent API between processes and the absence of inferred types of your channels and handlers. These are some of the things that interprocess comes to solve!
29 |
30 | # 💬 Features
31 | - 🚀 Best-in-class DX with a fully-typed API
32 | - 🧠 Enchanced and consistent API
33 | - 🔥 Type-safe and scalable
34 | - 🪄 Code splitting support
35 | - 🕸️ All edges connected (APIs to handle all processes)
36 | - 💖 `invoke` and `handle` methods in both processes with the same expected behavior
37 |
38 | # Requirements
39 | - Node.js >= 14.0.0
40 | - Electron >= 12.0.0
41 | - TypeScript >= 4.8.0
42 |
43 | # 💬 Installation
44 | In your terminal, run:
45 | - npm
46 | ```bash
47 | npm i interprocess
48 | ```
49 | - pnpm
50 | ```bash
51 | pnpm i interprocess
52 | ```
53 | - yarn
54 | ```bash
55 | yarn add interprocess
56 | ```
57 |
58 | # 💬 Usage
59 |
60 | Let's build something simple that can show you some of the interprocess's power!
61 |
62 | First, create the following folders at `src`:
63 |
64 | - `shared/ipcs` (this folder structure is optional)
65 |
66 | Then, create a file named as `index.ts` in the `ipcs` folder with the following content:
67 |
68 | ```ts
69 | import { createInterprocess } from 'interprocess'
70 |
71 | export const { ipcMain, ipcRenderer, exposeApiToGlobalWindow } =
72 | createInterprocess({
73 | main: {
74 | async getPing(_, data: 'ping') {
75 | const message = `from renderer: ${data} on main process`
76 |
77 | console.log(message)
78 |
79 | return message
80 | },
81 | },
82 |
83 | renderer: {
84 | async getPong(_, data: 'pong') {
85 | const message = `from main: ${data} on renderer process`
86 |
87 | console.log(message)
88 |
89 | return message
90 | },
91 | },
92 | })
93 | ```
94 |
95 | On the **main process**:
96 |
97 | > :warning: Don't forget to add `sandbox: false` to the BrowserWindow because it's required to load the preload script properly!
98 |
99 | ```ts
100 | import { BrowserWindow, app } from 'electron'
101 |
102 | import { ipcMain } from 'shared/ipcs'
103 |
104 | const { handle, invoke } = ipcMain
105 |
106 | app.whenReady().then(() => {
107 | const mainWindow = new BrowserWindow({
108 | webPreferences: {
109 | preload: path.join(__dirname, '../preload/index.js'),
110 | sandbox: false, // sandbox must be false
111 | },
112 | })
113 |
114 | handle.getPing()
115 |
116 | mainWindow.webContents.on('dom-ready', () => {
117 | invoke.getPong(mainWindow, 'pong')
118 | })
119 | })
120 | ```
121 |
122 | In the **preload script**:
123 |
124 | ```ts
125 | import { exposeApiToGlobalWindow } from 'shared/ipcs'
126 |
127 | const { key, api } = exposeApiToGlobalWindow({
128 | exposeAll: true, // expose handlers, invokers and removers
129 | })
130 |
131 | declare global {
132 | interface Window {
133 | [key]: typeof api
134 | }
135 | }
136 | ```
137 |
138 | On the **renderer process**:
139 |
140 | ```ts
141 | const { invoke, handle } = window.api
142 |
143 | invoke.getPing('ping')
144 | handle.getPong()
145 | ```
146 | This is a simple way to work with interprocess, but there's a lot of more cool features you can take advantage, like overrides, code splitting, invoker's response (for renderer and main process 🎉) and more. See the [Knowledge section for more](#-knowledge)
147 |
148 | # 💬 Knowledge
149 | - [Docs](https://interprocess.daltonmenezes.com/docs/getting-started/overview)
150 |
151 | - Examples
152 | - [Executable](https://github.com/daltonmenezes/interprocess/tree/main/apps/desktop)
153 | - [CodeSandbox (simple)](https://codesandbox.io/s/simple-607b6h?file=/src/ipcs.ts)
154 | - [CodeSandbox (advanced)](https://codesandbox.io/s/advanced-4qh0xb?file=/src/ipcs/index.ts)
155 |
156 |
157 | # 💬 Contributing
158 | > **Note**: contributions are always welcome, but always **ask first**, — please — before work on a PR.
159 |
160 | That said, there's a bunch of ways you can contribute to this project, like by:
161 |
162 | - :beetle: Reporting a bug
163 | - :page_facing_up: Improving the docs
164 | - :rotating_light: Sharing this project and recommending it to your friends
165 | - :dollar: Supporting this project on GitHub Sponsors or Patreon
166 | - :bug: Funding an issue on IssueHunt
167 | - :star2: Giving a star on this repository
168 |
169 | # License
170 |
171 | [MIT © Dalton Menezes](https://github.com/daltonmenezes/interprocess/blob/main/LICENSE)
172 |
--------------------------------------------------------------------------------
/apps/web/src/shared/constants/codes.ts:
--------------------------------------------------------------------------------
1 | export const codes = {
2 | ipcs: {
3 | finalState: `import { createInterprocess } from 'interprocess'
4 |
5 | export const { exposeApiToGlobalWindow, ipcMain, ipcRenderer } =
6 | createInterprocess({
7 | main: {
8 | async myIpcHandler(_, data: string) {
9 | // ... do something with the data when this handler is invoked by the renderer process
10 | return data.toUpperCase()
11 | },
12 | },
13 |
14 | renderer: {
15 | async myAnotherIpcHandler(_, data: string) {
16 | // ... do something with the data when this handler is invoked by the main process
17 | return data.toLowerCase()
18 | },
19 | },
20 | })
21 | `,
22 |
23 | firstCheckToShowIntelliSense: `import { createInterprocess } from 'interprocess'
24 |
25 | export const { exposeApiToGlobalWindow, ipcMain, ipcRenderer } =
26 | createInterprocess({
27 | `,
28 |
29 | secondCheckToShowIntelliSense: `import { createInterprocess } from 'interprocess'
30 |
31 | export const { exposeApiToGlobalWindow, ipcMain, ipcRenderer } =
32 | createInterprocess({
33 | main: {
34 | async myIpcHandler(_, data: string) {
35 | // ... do something with the data when this handler is invoked by the renderer process
36 | return data.toUpperCase()
37 | },
38 | },
39 |
40 | `,
41 | },
42 |
43 | main: {
44 | finalState: `// ... some imports
45 | import { ipcMain } from 'shared/ipcs'
46 |
47 | const { handle, invoke } = ipcMain
48 |
49 | app.whenReady().then(() => {
50 | const mainWindow = new BrowserWindow({
51 | webPreferences: {
52 | preload: path.join(__dirname, '../preload/index.js'),
53 | sandbox: false, // sandbox must be false
54 | },
55 | })
56 |
57 | handle.myIpcHandler(async (_, { myIpcHandler, data }) => {
58 | const result = await myIpcHandler(_, data)
59 |
60 | return result
61 | })
62 | // That could be handle.myIpcHandler()
63 | // if you don't need to do anything else
64 | // with the result here and it will call the handler directly
65 |
66 | mainWindow.webContents.on('dom-ready', () => {
67 | // invoke the myAnotherIpcHandler from renderer process
68 | invoke.myAnotherIpcHandler(mainWindow, 'LOWER-CASE IT FOR ME')
69 | })
70 | })
71 | `,
72 |
73 | firstCheckToShowIntelliSense: `// ... some imports
74 | import { ipcMain } from 'shared/ipcs'
75 |
76 | const { `,
77 |
78 | secondCheckToShowIntelliSense: `// ... some imports
79 | import { ipcMain } from 'shared/ipcs'
80 |
81 | const { handle, `,
82 |
83 | thirdCheckToShowIntelliSense: `// ... some imports
84 | import { ipcMain } from 'shared/ipcs'
85 |
86 | const { handle, invoke } = ipcMain
87 |
88 | app.whenReady().then(() => {
89 | const mainWindow = new BrowserWindow({
90 | webPreferences: {
91 | preload: path.join(__dirname, '../preload/index.js'),
92 | sandbox: false, // sandbox must be false
93 | },
94 | })
95 |
96 | handle.`,
97 |
98 | fourthCheckToShowIntelliSense: `// ... some imports
99 | import { ipcMain } from 'shared/ipcs'
100 |
101 | const { handle, invoke } = ipcMain
102 |
103 | app.whenReady().then(() => {
104 | const mainWindow = new BrowserWindow({
105 | webPreferences: {
106 | preload: path.join(__dirname, '../preload/index.js'),
107 | sandbox: false, // sandbox must be false
108 | },
109 | })
110 |
111 | handle.myIpcHandler(async (_, { `,
112 |
113 | fifthCheckToShowIntelliSense: `// ... some imports
114 | import { ipcMain } from 'shared/ipcs'
115 |
116 | const { handle, invoke } = ipcMain
117 |
118 | app.whenReady().then(() => {
119 | const mainWindow = new BrowserWindow({
120 | webPreferences: {
121 | preload: path.join(__dirname, '../preload/index.js'),
122 | sandbox: false, // sandbox must be false
123 | },
124 | })
125 |
126 | handle.myIpcHandler(async (_, { myIpcHandler, `,
127 |
128 | sixthCheckToShowIntelliSense: `// ... some imports
129 | import { ipcMain } from 'shared/ipcs'
130 |
131 | const { handle, invoke } = ipcMain
132 |
133 | app.whenReady().then(() => {
134 | const mainWindow = new BrowserWindow({
135 | webPreferences: {
136 | preload: path.join(__dirname, '../preload/index.js'),
137 | sandbox: false, // sandbox must be false
138 | },
139 | })
140 |
141 | handle.myIpcHandler(async (_, { myIpcHandler, data }) => {
142 | const result = await myIpcHandler(_, data)
143 |
144 | return result
145 | })
146 | // That could be handle.myIpcHandler()
147 | // if you don't need to do anything else
148 | // with the result here and it will call the handler directly
149 |
150 | mainWindow.webContents.on('dom-ready', () => {
151 | // invoke the myAnotherIpcHandler from renderer process
152 | invoke.`,
153 | },
154 |
155 | preload: {
156 | finalState: `import { exposeApiToGlobalWindow, ipcRenderer } from './ipcs'
157 |
158 | const { handle } = ipcRenderer
159 |
160 | // Call exposeApiToGlobalWindow with no arguments will expose only invokers to the api
161 | // If you want to expose all options, you can pass { exposeAll: true } object argument
162 | const { key, api } = exposeApiToGlobalWindow()
163 |
164 | // execute the myAnotherIpcHandler registration
165 | handle.myAnotherIpcHandler()
166 |
167 | declare global {
168 | interface Window {
169 | [key]: typeof api
170 | }
171 | }
172 | `,
173 | firstCheckToShowIntelliSense: `import { exposeApiToGlobalWindow, ipcRenderer } from './ipcs'
174 |
175 | const { handle } = ipcRenderer
176 |
177 | // Call exposeApiToGlobalWindow with no arguments will expose only invokers to the api
178 | // If you want to expose all options, you can pass { exposeAll: true } object argument
179 | const { key, api } = exposeApiToGlobalWindow()
180 |
181 | // execute the myAnotherIpcHandler registration
182 | handle.`,
183 | },
184 |
185 | renderer: {
186 | finalState: `const { invoke } = window.api
187 |
188 | // invoke the myIpcHandler from main process
189 | invoke.myIpcHandler('upper-case me, please!')
190 |
191 | `,
192 | firstCheckToShowIntelliSense: `const { invoke } = window.api
193 |
194 | // invoke the myIpcHandler from main process
195 | invoke.`,
196 | },
197 | }
198 |
--------------------------------------------------------------------------------
/apps/web/src/templates/Docs/styles.ts:
--------------------------------------------------------------------------------
1 | import { styled } from 'styles'
2 |
3 | import { Title as _Title } from 'components/Layout/Title'
4 | import { getPublicPath } from 'shared/utils'
5 |
6 | export const Section = styled('section', {
7 | display: 'flex',
8 | flexDirection: 'row',
9 | flex: 1,
10 | height: '100%',
11 | width: '100%',
12 | paddingBottom: '1.5rem',
13 | paddingTop: '3.7rem',
14 |
15 | pre: {
16 | fontFamily: 'inherit',
17 | lineHeight: '1.5',
18 | textAlign: 'left',
19 | width: '100%',
20 | color: '$accent-secondary',
21 | },
22 |
23 | '@bp4': {
24 | paddingTop: 0,
25 | },
26 | })
27 |
28 | export const MenuButton = styled('button', {
29 | display: 'flex',
30 |
31 | '@bp4': {
32 | display: 'none',
33 | },
34 |
35 | variants: {
36 | active: {
37 | true: {
38 | 'svg g': {
39 | stroke: '$accent-secondary',
40 | },
41 | },
42 | },
43 | },
44 | })
45 |
46 | export const Sidebar = styled('aside', {
47 | display: 'flex',
48 | flexDirection: 'column',
49 | flex: 1,
50 | width: '100%',
51 | maxWidth: '84%',
52 | height: '-webkit-fill-available',
53 | overflowY: 'auto',
54 | padding: '1.5rem',
55 | paddingTop: '3.3rem',
56 | fontSize: '1rem',
57 | lineHeight: '1.5rem',
58 | whiteSpace: 'pre-wrap',
59 | wordBreak: 'break-word',
60 | wordWrap: 'break-word',
61 | overflowWrap: 'break-word',
62 | hyphens: 'auto',
63 | top: '4.2rem',
64 | left: 0,
65 | zIndex: 999,
66 | position: 'fixed',
67 |
68 | background: '$shape-primary',
69 | border: '1px solid $border-primary',
70 | borderRadius: 15,
71 | borderTopLeftRadius: 0,
72 | borderBottomRightRadius: 0,
73 | boxShadow: '0 2px 2px 1px $colors$shadow-primary',
74 |
75 | ul: {
76 | display: 'flex',
77 | flexDirection: 'column',
78 | gap: '0.5rem',
79 | },
80 |
81 | li: {
82 | display: 'flex',
83 | flexDirection: 'column',
84 |
85 | div: {
86 | display: 'flex',
87 | flexDirection: 'column',
88 | gap: '0.5rem',
89 |
90 | span: {
91 | textTransform: 'capitalize',
92 | fontSize: '1.1rem',
93 | fontWeight: 700,
94 | color: '$accent-secondary',
95 |
96 | '@bp4': {
97 | fontSize: '1.2rem',
98 | },
99 | },
100 |
101 | a: {
102 | transition: 'all 0.2s ease-in-out',
103 | overflow: 'hidden',
104 | textOverflow: 'ellipsis',
105 | whiteSpace: 'nowrap',
106 | maxWidth: '100%',
107 | },
108 |
109 | '&:has(span) a': {
110 | marginLeft: '1rem',
111 | width: 'fit-content',
112 | },
113 |
114 | '&:has(span + div) a': {
115 | marginLeft: 'unset',
116 | },
117 |
118 | '&:has(span + div) div': {
119 | marginBottom: '0.5rem',
120 | },
121 |
122 | 'ul li': {
123 | marginLeft: '1rem',
124 |
125 | span: {
126 | fontSize: '1rem',
127 | },
128 | },
129 | },
130 | },
131 |
132 | '@bp4': {
133 | display: 'flex',
134 | top: 0,
135 | left: 0,
136 | position: 'relative',
137 | height: 'auto',
138 | zIndex: 0,
139 | maxWidth: '220px',
140 |
141 | borderTopLeftRadius: 15,
142 | borderTopRightRadius: 0,
143 | borderBottomRightRadius: 0,
144 | },
145 |
146 | transition: 'all 0.2s ease-in-out',
147 |
148 | variants: {
149 | visibility: {
150 | true: {
151 | transform: 'translateX(0)',
152 | },
153 |
154 | false: {
155 | transform: 'translateX(-100%)',
156 |
157 | '@bp4': {
158 | transform: 'translateX(0)',
159 | minWidth: 'fit-content',
160 | },
161 | },
162 | },
163 | },
164 | })
165 |
166 | export const Content = styled('div', {
167 | display: 'flex',
168 | flex: 1,
169 | flexDirection: 'column',
170 | justifyContent: 'space-between',
171 | padding: '1rem 1.5rem',
172 | gap: '1rem',
173 | backgroundColor: '$shape-primary',
174 | borderRadius: 15,
175 | border: '1px solid $border-primary',
176 | boxShadow: '0 2px 2px 1px $colors$shadow-primary',
177 | width: '100%',
178 | minHeight: '100%',
179 |
180 | '@bp4': {
181 | padding: '2rem 3rem',
182 | borderTopLeftRadius: 0,
183 | borderBottomLeftRadius: 0,
184 | borderLeft: '1px solid black',
185 | },
186 | })
187 |
188 | export const Header = styled('header', {
189 | display: 'flex',
190 | flex: 1,
191 | flexDirection: 'column',
192 | alignSelf: 'center',
193 | alignItems: 'center',
194 | minHeight: '100%',
195 | justifyContent: 'space-between',
196 | gap: '1rem',
197 |
198 | div: {
199 | display: 'flex',
200 | flexDirection: 'row',
201 | gap: '1rem',
202 | alignItems: 'center',
203 | },
204 |
205 | 'div:first-of-type': {
206 | width: '100%',
207 | justifyContent: 'flex-start',
208 | },
209 |
210 | 'div:last-of-type': {
211 | width: '100%',
212 | justifyContent: 'flex-end',
213 | },
214 |
215 | a: {
216 | transition: 'filter 0.2s ease-in-out',
217 |
218 | '&:hover': {
219 | filter: 'brightness(0.7)',
220 | },
221 | },
222 |
223 | '@bp2': {
224 | flexDirection: 'row',
225 | gap: 0,
226 |
227 | div: {
228 | flexDirection: 'row',
229 | },
230 |
231 | 'div:first-of-type': {
232 | width: 'fit-content',
233 | },
234 | },
235 | })
236 |
237 | export const Title = styled(_Title, {
238 | fontSize: '1.3rem',
239 | letterSpacing: '0.1rem',
240 | textTransform: 'none',
241 | })
242 |
243 | export const Article = styled('article', {
244 | display: 'flex',
245 | flexDirection: 'column',
246 |
247 | h1: {
248 | maxWidth: 'fit-content',
249 | fontSize: '1.7rem',
250 | letterSpacing: 0,
251 | transition: 'all 0.2s ease-in-out',
252 | },
253 |
254 | 'h1:first-of-type:hover': {
255 | filter: 'brightness(0.7)',
256 | },
257 |
258 | 'h1, h2, h3, h4, h5, h6': {
259 | marginVertical: '1rem',
260 | position: 'relative',
261 |
262 | '&:hover a::before': {
263 | content: '',
264 | display: 'block',
265 | width: 15,
266 | height: 15,
267 | zIndex: 1,
268 | top: '20%',
269 | right: 'calc(100% + 0.2rem)',
270 | background: `url(${getPublicPath('/link.svg')}) no-repeat center`,
271 | backgroundSize: 'contain',
272 | position: 'absolute',
273 | },
274 | },
275 |
276 | p: {
277 | fontSize: '1rem',
278 | color: '$text-support',
279 | textAlign: 'left',
280 | },
281 |
282 | ul: {},
283 |
284 | li: {
285 | marginLeft: '1rem',
286 | listStyleType: 'circle',
287 | },
288 |
289 | strong: {
290 | color: '$accent-secondary',
291 |
292 | '&:nth-of-type(even)': {
293 | color: '$accent-primary',
294 | },
295 | },
296 | })
297 |
--------------------------------------------------------------------------------