Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .changeset/perfect-eagles-add.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@reactlit/radix': patch
'@reactlit/core': patch
'@reactlit/vanilla': patch
---

fix to tunnel ordering
4 changes: 2 additions & 2 deletions apps/reactlit-examples/src/pages/hello-world/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { Reactlit, useReactlitState } from '@reactlit/core';
import { Inputs } from '@reactlit/radix';
import { Inputs, Label } from '@reactlit/radix';

export default function HelloWorld() {
const [appState, setAppState] = useReactlitState({
Expand All @@ -11,8 +11,8 @@ export default function HelloWorld() {
display(<div className="text-2xl">Hello World</div>);
const name = view(
'name',
Label('Name'),
Inputs.Text({
label: 'Name',
placeholder: 'Enter your name',
})
);
Expand Down
55 changes: 32 additions & 23 deletions apps/reactlit-examples/src/pages/radix-inputs/index.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { useDebug } from '@/components/debug-toggle';
import { Debug, useDebug } from '@/components/debug-toggle';
import {
Badge,
ChevronDownIcon,
Expand Down Expand Up @@ -60,14 +60,16 @@ const ResultsWrapper: Wrapper = ({ children }) => {
const [open, setOpen] = useState(true);
return (
<div
className={`overlay ${open ? 'open' : ''}`}
className={`flex flex-col overlay ${open ? 'open' : ''}`}
onClick={() => setOpen(!open)}
>
<h3 className="overlay-header">
{open ? <ChevronDownIcon /> : <ThickChevronRightIcon />}
Results
</h3>
<DataList.Root orientation={'vertical'}>{children}</DataList.Root>
<div className="overflow-y-auto flex-auto">
<DataList.Root orientation={'vertical'}>{children}</DataList.Root>
</div>
</div>
);
};
Expand All @@ -84,6 +86,7 @@ export default function RadixInputs() {
color: 'red',
slider: 0,
rangeSlider: [20, 80],
enableLetter: true,
});
const debug = useDebug();
return (
Expand All @@ -92,44 +95,53 @@ export default function RadixInputs() {
{async ({ display, view }) => {
display(<div className="text-2xl">Inputs test</div>);
const [results] = view('results', ResultsWrapper, LayoutView(1));
const displayResult = (label: string, value: React.ReactNode) => {
results.display(Debug, DisplayLabel(label), value);
};
const name = view(
'name',
Label('Name'),
Inputs.Text({
placeholder: 'Enter your name',
})
);
results.display(DisplayLabel('Name'), name);
displayResult('Name', name);
const bio = view(
'bio',
Label('Bio'),
Inputs.TextArea({
placeholder: 'Enter your bio',
})
);
results.display(DisplayLabel('Bio'), bio);
displayResult('Bio', bio);
const number = view(
'number',
Label('Pick any numbers'),
Inputs.Check({ one: '1', two: '2', three: '3' })
);
results.display(DisplayLabel('Numbers'), number);
const letter = view(
'letter',
Label('Pick one Letter'),
Inputs.Radio(['A', 'B', 'C'])
);
results.display(DisplayLabel('Letter'), letter);
displayResult('Numbers', number);
if (
view(
'enableLetter',
Label('Letter Selection'),
<div />,
Inputs.Switch()
)
) {
const letter = view(
'letter',
Label('Pick one Letter'),
Inputs.Radio(['A', 'B', 'C'])
);
displayResult('Letter', letter);
}
const color = view(
'color',
Label('Pick a color'),
<div />,
Inputs.Select(['red', 'blue', 'green'] as const)
);
results.display(
DisplayLabel('Color'),
<Badge color={color}>{color}</Badge>
);
displayResult('Color', <Badge color={color}>{color}</Badge>);
const slider = view(
'slider',
Label('Slider'),
Expand All @@ -138,7 +150,7 @@ export default function RadixInputs() {
max: 100,
})
);
results.display(DisplayLabel('Slider'), slider);
displayResult('Slider', slider);
const rangeSlider = view(
'rangeSlider',
Label('Range Slider'),
Expand All @@ -147,10 +159,7 @@ export default function RadixInputs() {
max: 100,
})
);
results.display(
DisplayLabel('Range Slider'),
rangeSlider.join(' - ')
);
displayResult('Range Slider', rangeSlider.join(' - '));
const countries = await fetchCountries();
display(<div className="font-semibold">Select a country</div>);
const filteredCountries = view(
Expand All @@ -168,8 +177,8 @@ export default function RadixInputs() {
className: 'h-[300px]',
})
);
results.display(
DisplayLabel('Country'),
displayResult(
'Country',
<>
{selectedCountry?.code ? (
<img
Expand Down
14 changes: 4 additions & 10 deletions apps/reactlit-examples/src/pages/todo-list/index.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { Button, Callout, Spinner } from '@radix-ui/themes';
import { DataFetchingPlugin, useReactlit } from '@reactlit/core';
import { DefaultRadixWrapper, Inputs } from '@reactlit/radix';
import { DefaultRadixWrapper, Inputs, Label } from '@reactlit/radix';
import { InfoIcon } from 'lucide-react';
import { TodoService } from '../../mocks/todos';

Expand Down Expand Up @@ -67,17 +67,11 @@ export default function TodoList() {
set('task', selectedTodo.task);
set('completed', selectedTodo.completed);
}
const task = view(
'task',
Inputs.Text({
label: 'Task',
})
);
const task = view('task', Label('Task'), Inputs.Text());
const completed = view(
'completed',
Inputs.Switch({
label: 'Completed',
})
Label('Completed'),
Inputs.Switch()
);
view(
'updaing',
Expand Down
2 changes: 1 addition & 1 deletion apps/reactlit-examples/src/styles/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ tr.rt-TableRow:nth-child(even) {
}

.overlay.open {
max-height: 45rem;
max-height: 80%;
}

.overlay-header {
Expand Down
8 changes: 6 additions & 2 deletions libs/core/src/inputs/layout.view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -46,8 +46,12 @@ export function createLayoutSlot<T extends StateBase = StateBase>(
ctx: Pick<ReactlitContext<T>, 'display' | 'view'>,
t: ReturnType<typeof tunnel>
): LayoutSlot<T> {
const TunnelWrapper: Wrapper = ({ stateKey, children }) => {
return <t.In childKey={stateKey}>{children}</t.In>;
const TunnelWrapper: Wrapper = ({ stateKey, position, children }) => {
return (
<t.In childKey={stateKey} order={position}>
{children}
</t.In>
);
};
return {
display(...args) {
Expand Down
6 changes: 4 additions & 2 deletions libs/core/src/reactlit.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@ import { useReactlitSet } from './builtins/set';
import { ReactlitProps, StateBase } from './builtins/types';
import { useReactlitView } from './builtins/view';
import { useReactlitState } from './hooks/use-reactlit-state';
import { useIsomorphicLayoutEffect } from './utils/use-isomorphic-layout-effect';
import { uniqueBy } from './utils/unique-by';

const defaultRenderError = ({ error }) => (
<div style={{ color: 'red' }}>
Expand Down Expand Up @@ -55,7 +57,7 @@ export function Reactlit<T extends StateBase = any>({

const renderLock = useRef(false);
const renderAfter = useRef(false);
useEffect(() => {
useIsomorphicLayoutEffect(() => {
async function runScript() {
setRendering(true);
if (renderLock.current) {
Expand Down Expand Up @@ -103,7 +105,7 @@ export function Reactlit<T extends StateBase = any>({

return (
<>
{renderState.elements.map(([key, node]) => (
{uniqueBy(renderState.elements, '0').map(([key, node]) => (
<Fragment key={key}>{node}</Fragment>
))}
{renderLoading?.(rendering) ?? <></>}
Expand Down
34 changes: 21 additions & 13 deletions libs/core/src/utils/tunnel.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
import React, { Fragment } from 'react';
import { create, StoreApi } from 'zustand';
import { useIsomorphicLayoutEffect } from './use-isomorphic-layout-effect';
import { uniqueBy } from './unique-by';

// modified from tunnel-rat

type Props = { childKey: string; children: React.ReactNode };
type Props = { childKey: string; order: number; children: React.ReactNode };

type State = {
current: Array<{ childKey: string; node: React.ReactNode }>;
current: Array<Props>;
version: number;
set: StoreApi<State>['setState'];
};

function sortByOrder(array: Props[]) {
return array.sort((a, b) => a.order - b.order);
}

export default function tunnel() {
const useStore = create<State>((set) => ({
current: new Array<{ childKey: string; node: React.ReactNode }>(),
current: new Array<Props>(),
version: 0,
set,
}));

return {
In: ({ childKey, children }: Props) => {
In: ({ childKey, order, children }: Props) => {
const set = useStore((state) => state.set);
const version = useStore((state) => state.version);

Expand All @@ -40,22 +45,25 @@ export default function tunnel() {
set(({ current }) => {
const existing = current.findIndex((c) => c.childKey === childKey);
return {
current:
current: sortByOrder(
existing !== -1
? [
...current.slice(0, existing),
{ childKey, node: children },
{ childKey, order, children },
...current.slice(existing + 1),
]
: [...current, { childKey, node: children }],
: [...current, { childKey, order, children }]
),
};
});

// remove the cleanup logic so that nodes stay in position, the key logic keeps things from getting too messy
// return () =>
// set(({ current }) => ({
// current: current.filter((c) => c.node !== children),
// }));
return () =>
set(({ current }) => {
return {
current: current.filter((c) => c.childKey !== childKey),
};
});
}, [children, version]);

return null;
Expand All @@ -65,8 +73,8 @@ export default function tunnel() {
const current = useStore((state) => state.current);
return (
<>
{current.map((c) => (
<Fragment key={c.childKey}>{c.node}</Fragment>
{uniqueBy(current, 'childKey').map((c) => (
<Fragment key={c.childKey}>{c.children}</Fragment>
))}
</>
);
Expand Down
5 changes: 5 additions & 0 deletions libs/core/src/utils/unique-by.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
export function uniqueBy<T>(array: T[], key: keyof T) {
return array.filter(
(item, index, self) => index === self.findIndex((t) => t[key] === item[key])
);
}
1 change: 0 additions & 1 deletion libs/radix/src/inputs/search.input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ export function SearchInput<T>(
(viewProps) => (
<TextInputComponent
{...viewProps}
label={'Search'}
placeholder={'Search...'}
type={'search' as const}
{...props}
Expand Down
5 changes: 1 addition & 4 deletions libs/radix/src/inputs/switch.input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import { LabelType } from '../label';
export type SwitchInputProps = Omit<
SwitchProps,
'checked' | 'onCheckedChange' | 'value' | 'onValueChange'
> & {
label?: LabelType;
};
>;

export const SwitchInputComponent = ({
value,
stateKey,
setValue,
label,
display,
view,
...props
Expand Down
2 changes: 0 additions & 2 deletions libs/radix/src/inputs/text.input.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import { useDebouncedCallback } from 'use-debounce';
import { LabelType } from '../label';

export type TextInputProps = Omit<TextField.RootProps, 'value' | 'children'> & {
label?: LabelType;
children?:
| React.ReactNode
| ((props: ViewComponentProps<string>) => React.ReactNode);
Expand All @@ -17,7 +16,6 @@ export const TextInputComponent = ({
stateKey,
setValue,
onChange,
label,
debounceDelay = 200,
children,
display,
Expand Down