Skip to content

Commit

Permalink
added useStyle
Browse files Browse the repository at this point in the history
  • Loading branch information
atellmer committed Jul 23, 2022
1 parent c9a1596 commit 0fb5c99
Show file tree
Hide file tree
Showing 21 changed files with 270 additions and 118 deletions.
52 changes: 46 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,7 +57,7 @@ import {
useState,
useDeferredValue,
} from '@dark-engine/core';
import { render, createPortal } from '@dark-engine/platform-browser';
import { render, createPortal, useStyle } from '@dark-engine/platform-browser';
```
## Shut up and show me your code!

Expand Down Expand Up @@ -316,16 +316,33 @@ If a child element is passed to the component, it will appear in props as slot:

```tsx
const App = createComponent(({ slot }) => {
return [
<header>Header</header>,
<div>{slot}</div>,
<footer>Footer</footer>,
];
return (
<>
<header>Header</header>
<div>{slot}</div>
<footer>Footer</footer>
</>
);
});

render(<App>Content</App>, document.getElementById('root'));
```

### Events
Dark uses the standard DOM event system, but written in camelCase. A handler is passed to the event attribute in the form of a function, which receives a synthetic event containing a native event. Synthetic events are needed in order to emulate the operation of stopPropagation. The emulation is required because, for performance reasons, Dark uses native event delegation to the document element instead of the original element. For example, if you subscribe to a button click event, the event will be tracked on the entire document, not on that button.

```tsx
import { type SyntheticEvent } from '@dark-engine/platform-browser';
```

```tsx
const handleInput = (e: SyntheticEvent<InputEvent, HTMLInputElement>) => setValue(e.target.value);
const handleClick = (e: SyntheticEvent<MouseEvent, HTMLButtonElement>) => console.log('click');

<input value={value} onInput={handleInput} />
<button onClick={handleClick}>Click me</button>
```

### Hooks
Hooks are needed to bring components to life: give them an internal state, start some actions, and so on. The basic rule for using hooks is to use them at the top level of the component, i.e. do not nest them inside other functions, cycles, conditions. This is a necessary condition, because hooks are not magic, but work based on array indices.

Expand Down Expand Up @@ -743,6 +760,29 @@ const App = createComponent(() => {
});
```

### Styles
In Dark for the Browser, styling is done just like in normal HTML using the style and class attributes. You can also use the useStyles hook, which returns an object with styles. This hook differs from the usual template literals in that it internally minifies the style by removing extra spaces (memoization is used), and also, if you have the syntax highlighting plugin for styled-components installed, highlights the style.

```tsx
import { useStyle } from '@dark-engine/platform-browser';
```

```tsx
const styles = useStyle(styled => ({
container: styled`
width: 100%;
background-color: ${color};
`,
item: styled`
color: purple;
`,
}));

<div style={styles.container}>
<span style={styled.item}>I'm styled!</span>
</div>
```

### Portals

This is a browser environment-specific ability to redirect the rendering flow to another element in the DOM tree. The main purpose is modal windows, dropdown menus and everything where it is necessary to avoid the possibility of being overlapped by the parent container due to configured css overflow.
Expand Down
139 changes: 69 additions & 70 deletions examples/modal-window/index.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,20 @@
import { h, createComponent, useEffect, useState, useMemo, Fragment } from '@dark-engine/core';
import { render, createPortal } from '@dark-engine/platform-browser';
import { render, createPortal, useStyle } from '@dark-engine/platform-browser';

const Overlay = createComponent(() => {
const style = `
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(1, 1, 1, 0.5);
z-index: 10000;
`;

return <div style={style} />;
const style = useStyle(styled => ({
container: styled`
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
background-color: rgba(1, 1, 1, 0.5);
z-index: 10000;
`,
}));

return <div style={style.container} />;
});

type ModalProps = {
Expand All @@ -22,76 +24,73 @@ type ModalProps = {

const Modal = createComponent<ModalProps>(({ isOpen, slot, onRequestClose }) => {
const host = useMemo<HTMLDivElement>(() => document.createElement('div'), []);

if (isOpen && document.body !== host.parentElement) {
document.body.appendChild(host);
} else if (!isOpen && document.body === host.parentElement) {
document.body.removeChild(host);
}

const containerStyle = `
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
overflow-y: auto;
padding: 16px;
`;

const modalStyle = `
position: relative;
width: 600px;
min-height: 300px;
max-width: 100%;
background-color: #fff;
color: #000;
margin: auto;
z-index: 10000;
border-radius: 4px;
`;

const modalHeaderStyle = `
padding: 32px 32px 0 32px;
`;

const closeButtonStyle = `
position: absolute;
top: 4px;
right: 4px;
width: 32px;
height: 32px;
border-radius: 50%;
cursor: pointer;
background-color: #666;
border: 0;
color: #fff;
`;

const modalBodyStyle = `
position: relative;
padding: 32px;
`;
const style = useStyle(styled => ({
container: styled`
position: fixed;
top: 0;
right: 0;
bottom: 0;
left: 0;
display: flex;
width: 100%;
height: 100%;
overflow-y: auto;
padding: 16px;
`,
modal: styled`
position: relative;
width: 600px;
min-height: 300px;
max-width: 100%;
background-color: #fff;
color: #000;
margin: auto;
z-index: 10000;
border-radius: 4px;
`,
modalHeader: styled`
padding: 32px 32px 0 32px;
`,
closeButton: styled`
position: absolute;
top: 4px;
right: 4px;
width: 32px;
height: 32px;
border-radius: 50%;
background-color: #666;
border: 0;
color: #fff;
cursor: pointer;
`,
modalBody: styled`
position: relative;
padding: 32px;
`,
}));

const renderModal = () => {
return (
<div style={containerStyle}>
<div style={style.container}>
<Overlay />
<div style={modalStyle}>
<div style={modalHeaderStyle}>
<button style={closeButtonStyle} onClick={onRequestClose}>
<div style={style.modal}>
<div style={style.modalHeader}>
<button style={style.closeButton} onClick={onRequestClose}>
X
</button>
</div>
<div style={modalBodyStyle}>{slot}</div>
<div style={style.modalBody}>{slot}</div>
</div>
</div>
);
};

if (isOpen && document.body !== host.parentElement) {
document.body.appendChild(host);
} else if (!isOpen && document.body === host.parentElement) {
document.body.removeChild(host);
}

return isOpen ? createPortal(renderModal(), host) : null;
});

Expand Down
1 change: 1 addition & 0 deletions examples/sierpinski-triangle/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
}
body {
color: black;
overflow: hidden;
}
</style>
</head>
Expand Down
60 changes: 32 additions & 28 deletions examples/sierpinski-triangle/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import {
Fragment,
TaskPriority,
} from '@dark-engine/core';
import { render } from '@dark-engine/platform-browser';
import { render, useStyle } from '@dark-engine/platform-browser';

startFPSMonitor();
startMemMonitor();
Expand All @@ -23,20 +23,22 @@ const targetSize = 25;
const Dot = createComponent<{ size: number; x: number; y: number }>(props => {
const [hover, setHover] = useState(false, TaskPriority.HIGH);
const s = props.size * 1.3;
const style = `
position: absolute;
background-color: #61dafb;
font: normal 15px sans-serif;
text-align: center;
cursor: pointer;
width: ${s}px;
height: ${s}px;
left: ${props.x}px;
top: ${props.y}px;
border-radius: ${s / 2}px;
line-height: ${s}px;
background-color: ${hover ? 'yellow' : '#61dafb'};
`;
const style = useStyle(styled => ({
dot: styled`
position: absolute;
background-color: #61dafb;
font: normal 15px sans-serif;
text-align: center;
cursor: pointer;
width: ${s}px;
height: ${s}px;
left: ${props.x}px;
top: ${props.y}px;
border-radius: ${s / 2}px;
line-height: ${s}px;
background-color: ${hover ? 'yellow' : '#61dafb'};
`,
}));

const enter = useCallback(() => {
setHover(true);
Expand All @@ -47,7 +49,7 @@ const Dot = createComponent<{ size: number; x: number; y: number }>(props => {
}, []);

return (
<div style={style} onMouseEnter={enter} onMouseLeave={leave}>
<div style={style.dot} onMouseEnter={enter} onMouseLeave={leave}>
{hover ? `* ${Text(props.slot)} *` : Text(props.slot)}
</div>
);
Expand Down Expand Up @@ -102,20 +104,22 @@ const App = createComponent<AppProps>(props => {

const tick = useCallback(() => setSeconds(seconds => (seconds % 10) + 1), []);

const containerStyle = `
position: absolute;
transform-origin: 0 0;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
background-color: #eee;
transform: ${'scaleX(' + scale / 2.1 + ') scaleY(0.7) translateZ(0.1px)'};
zoom: 1;
`;
const style = useStyle(styled => ({
container: styled`
position: absolute;
transform-origin: 0 0;
left: 50%;
top: 50%;
width: 10px;
height: 10px;
background-color: #eee;
transform: ${'scaleX(' + scale / 2.1 + ') scaleY(0.7) translateZ(0.1px)'};
zoom: 1;
`,
}));

return (
<div style={containerStyle}>
<div style={style.container}>
<MemoSierpinskiTriangle x={0} y={0} s={1000}>
{seconds}
</MemoSierpinskiTriangle>
Expand Down
2 changes: 1 addition & 1 deletion packages/core/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dark-engine/core",
"version": "0.2.0",
"version": "0.3.0",
"description": "Dark is lightweight (10 Kb gzipped) component-and-hook-based UI rendering engine for javascript apps without dependencies and written in Typescript 💫",
"author": "AlexPlex",
"license": "MIT",
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/component/model.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export type ComponentOptions<P extends StandardComponentProps> = Readonly<{
shouldUpdate?: (props: P, nextProps: P) => boolean;
}>;

export type StandardComponentProps = Readonly<Partial<{ [key: string]: any }>> & KeyProps & SlotProps & RefProps;
export type StandardComponentProps = KeyProps & SlotProps & RefProps;

export type KeyProps = {
key?: DarkElementKey;
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/suspense/suspense.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ const SuspenseContext = createContext<SuspenseContextValue>({

const Suspense = createComponent<SuspenseProps>(({ fallback, slot }) => {
if (!fallback) {
throw new Error(`Suspense fallback doesn't found`);
throw new Error(`Suspense fallback not found`);
}
const { isLoaded: isSuspenseLoaded } = useContext(SuspenseContext);
const [isLoaded, setIsLoaded] = useState(false);
Expand Down
2 changes: 1 addition & 1 deletion packages/core/src/use-callback/use-callback.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ test('use-callback returns function', () => {
return null;
});

render(App({ count: 1 }), host);
render(App(), host);
waitNextIdle();
expect(typeof handler).toBe('function');
});
2 changes: 1 addition & 1 deletion packages/core/umd/dark-core.development.js

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion packages/core/umd/dark-core.development.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/umd/dark-core.production.min.js

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/core/umd/dark-core.production.min.js.map

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion packages/platform-browser/package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "@dark-engine/platform-browser",
"version": "0.2.0",
"version": "0.3.0",
"description": "Dark is lightweight (10 Kb gzipped) component-and-hook-based UI rendering engine for javascript apps without dependencies and written in Typescript 💫",
"author": "AlexPlex",
"license": "MIT",
Expand Down
1 change: 1 addition & 0 deletions packages/platform-browser/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export { render } from './render';
export { createPortal } from './portal';
export { useStyle } from './use-style';
export type { SyntheticEvent } from './events';
Loading

0 comments on commit 0fb5c99

Please sign in to comment.