Skip to content

Commit

Permalink
feat: rtl support
Browse files Browse the repository at this point in the history
  • Loading branch information
Ali Garajian committed Apr 13, 2024
1 parent 78b7ed4 commit 4a963e9
Show file tree
Hide file tree
Showing 7 changed files with 84 additions and 18 deletions.
1 change: 1 addition & 0 deletions apps/docs/pages/api-reference/react/use-grid.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ const Page = () => {
| getItemData | function | No | Callback for grid item data |
| onLoadMore | function | No | Renders an area which triggers the callback when scrolled into view |
| loadMoreSize | number | No | Set the size of the load more trigger |
| rtl | boolean | No | Set to true for rtl direction |
36 changes: 33 additions & 3 deletions apps/web/components/demo/controls.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ const schema = z.object({
count: z.number().int()
}),
padding: z.number(),
gap: z.number()
gap: z.number(),
rtl: z.boolean()
});

type Controls = z.infer<typeof schema>;
Expand All @@ -34,7 +35,8 @@ export const defaults: Controls = {
size: { enabled: false, width: 140, height: 140 },
columns: { enabled: true, count: isMobile ? 3 : 5 },
padding: 12,
gap: 8
gap: 8,
rtl: false
};

export const Controls = ({
Expand Down Expand Up @@ -233,7 +235,9 @@ export const Controls = ({
id="gap"
type="number"
{...field}
onChange={(e) => field.onChange(Number(e.target.value))}
onChange={(e) =>
field.onChange(Number(e.target.value))
}
onBlur={async () => {
field.onBlur();
await handleSubmit();
Expand All @@ -244,6 +248,32 @@ export const Controls = ({
)}
/>
</div>

<div className="flex items-center">
<Label htmlFor="rtl" className="flex-1">
Rtl
</Label>
<FormField
control={form.control}
name="rtl"
render={({ field }) => (
<FormItem>
<FormControl>
<Checkbox
id="rtl"
{...field}
defaultChecked={field.value}
value={field.value?.toString()}
onCheckedChange={async (checked) => {
field.onChange(checked)
await handleSubmit();
}}
/>
</FormControl>
</FormItem>
)}
/>
</div>
</Section>
</div>
</form>
Expand Down
15 changes: 11 additions & 4 deletions apps/web/components/demo/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -33,20 +33,27 @@ export const Demo = () => {
}
: {}),
padding: controls.padding,
gap: controls.gap
gap: controls.gap,
rtl: controls.rtl,
});

return (
<Window>
<Controls controls={controls} onChange={setControls} />

<div ref={ref} className="h-full overflow-auto">
<div
ref={ref}
className="h-full overflow-auto"
dir={controls.rtl ? 'rtl' : 'ltr'}
>
<Grid grid={grid}>
{(index) => (
<div
key={index}
className="border-border/80 bg-accent h-full w-full rounded-lg border"
/>
className="border-border/80 bg-accent flex h-full w-full items-center justify-center rounded-lg border text-gray-500"
>
{index + 1}
</div>
)}
</Grid>
</div>
Expand Down
8 changes: 8 additions & 0 deletions packages/core/src/grid.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,10 @@ export interface DefaultGridProps<
* Horizontal mode places items in rows from top to bottom. `onLoadMore` area is placed on the x-axis.
*/
horizontal?: boolean;
/**
* @default false
*/
rtl?: boolean;
/**
* Callback function for grid item `id` in `getItem` function.
*/
Expand Down Expand Up @@ -120,6 +124,8 @@ export class Grid<
padding: Omit<GridPadding, 'x' | 'y'> = {};
gap: GridGap = {};

rtl = false;

constructor(props: GridProps<IdT, DataT>) {
this.options = props;
this.initOptions();
Expand Down Expand Up @@ -178,6 +184,8 @@ export class Grid<

this.itemWidth = typeof size === 'object' ? size.width : size;
this.itemHeight = typeof size === 'object' ? size.height : size;

this.rtl = !!this.options.rtl;
};

measure = () => {
Expand Down
1 change: 1 addition & 0 deletions packages/react/src/components/grid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export const Grid = ({ grid, children }: GridProps) => {
return (
<div
ref={ref}
dir={grid.options.rtl ? 'rtl' : 'ltr'}
style={{
position: 'relative',
width: `${columnVirtualizer.getTotalSize()}px`,
Expand Down
11 changes: 9 additions & 2 deletions packages/react/src/useGrid.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,8 @@ export const useGrid = <
const node = scrollRef.current;
if (!node || !observeGrid) return;

// NOTE: node.clientHeight & node.clientWidth hold the same value,
// plus, this wouldn't work for rotated elements
const { width, height } = node.getBoundingClientRect();

const {
Expand Down Expand Up @@ -106,7 +108,8 @@ export const useGrid = <
const style = {
position: 'absolute',
top: '0px',
left: '0px',
left: !grid.rtl ? '0px' : 'auto',
right: grid.rtl ? '0px' : 'auto',
width: size.width !== undefined ? `${size.width}px` : '100%',
height: size.height !== undefined ? `${size.height}px` : '100%',
transform: `translateX(${translate.x}px) translateY(${translate.y}px)`,
Expand All @@ -125,7 +128,11 @@ export const useGrid = <
}: {
virtualizer?: Virtualizer<HTMLElement, Element>;
} = {}) => {
const position = grid.options.horizontal ? 'right' : 'bottom';
const position = grid.options.horizontal
? grid.options.rtl
? 'left'
: 'right'
: 'bottom';

const getSize = grid.options.horizontal
? getLoadMoreTriggerWidth
Expand Down
30 changes: 21 additions & 9 deletions packages/shared/src/util.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,14 +19,17 @@ export const getRowVirtualizerOptions = (
};

export const getColumnVirtualizerOptions = (
grid: Pick<Core.Grid, 'totalColumnCount' | 'getItemWidth' | 'padding'>
grid: Pick<
Core.Grid,
'totalColumnCount' | 'getItemWidth' | 'padding' | 'options'
>
) => {
return {
horizontal: true,
count: grid.totalColumnCount,
estimateSize: grid.getItemWidth,
paddingStart: grid.padding.left,
paddingEnd: grid.padding.right
paddingStart: grid.options.rtl ? grid.padding.right : grid.padding.left,
paddingEnd: grid.options.rtl ? grid.padding.left : grid.padding.right
} satisfies PartialVirtualizerOptions;
};

Expand Down Expand Up @@ -93,7 +96,10 @@ export const getVirtualItemIndex = (
};

export const getVirtualItemStyle = (
grid: Pick<Core.Grid, 'padding' | 'gap' | 'itemWidth' | 'itemHeight'>,
grid: Pick<
Core.Grid,
'padding' | 'gap' | 'itemWidth' | 'itemHeight' | 'options'
>,
{ row, column, scrollMargin }: GetVirtualItemProps
) => {
const gap = {
Expand All @@ -117,14 +123,16 @@ export const getVirtualItemStyle = (
};

const padding = {
left: gridPadding.left + offsetPadding + gap.x,
right: gridPadding.right + offsetPadding,
left: gridPadding.left + offsetPadding + (!grid.options.rtl ? gap.x : 0),
right: gridPadding.right + offsetPadding + (grid.options.rtl ? gap.x : 0),
top: gridPadding.top + gap.y,
bottom: gridPadding.bottom
};

const translate = {
x: (column?.start ?? 0) - (scrollMargin?.left ?? 0),
x:
((column?.start ?? 0) - (scrollMargin?.left ?? 0)) *
(grid.options.rtl ? -1 : 1),
y: (row?.start ?? 0) - (scrollMargin?.top ?? 0)
};

Expand All @@ -150,7 +158,8 @@ export const getLoadMoreTriggerHeight = (
};

export const getLoadMoreTriggerWidth = (
props: GetLoadMoreTriggerProps & Pick<Core.Grid, 'totalColumnCount'>
props: GetLoadMoreTriggerProps &
Pick<Core.Grid, 'totalColumnCount' | 'options'>
) => {
if (props.totalColumnCount === props.columnCount) return props.size;

Expand All @@ -162,7 +171,10 @@ export const getLoadMoreTriggerWidth = (

const virtualizerWidth = props.virtualizer.getTotalSize();

const triggerWidth = virtualizerWidth - rect.left + loadMoreWidth;
const triggerWidth =
virtualizerWidth -
(props.options.rtl ? rect.right : rect.left) +
loadMoreWidth;

return Math.min(virtualizerWidth, triggerWidth);
};

0 comments on commit 4a963e9

Please sign in to comment.