Lemoncode / quickmock

MIT License
20 stars 13 forks source link

Create a listbox as Konva Component #27

Closed brauliodiez closed 1 month ago

brauliodiez commented 2 months ago

Create a ListBox for konva component

image

We have got the following listbox SVG component:

https://github.com/brauliodiez/konba-playground/blob/main/public/widgets/listbox.svg?short_path=c530fc9

Implement a React.konba component that takes into consideration resizing, take a look to the combobox konba implementation on Quick Mock

https://github.com/Lemoncode/quickmock/blob/main/src/common/components/front-components/combobox-shape.tsx

Add a solid white background to the input inner content.

Right now we will support dynamic resizing, but we won't bother about adding a text property.

A possible starting point:

import React, { forwardRef, useState, useRef, useEffect } from 'react';
import { Group, Rect, Text, Line } from 'react-konva';
import { ShapeConfig } from 'konva/lib/Shape';

interface ListBoxShapeProps extends ShapeConfig {
  id: string;
  x: number;
  y: number;
  width: number;
  height: number;
  items: string[];
  onSelected: (id: string, itemIndex: number) => void;
}

export const ListBoxShape = forwardRef<any, ListBoxShapeProps>(
  ({ x, y, width, height, id, items, onSelected, ...shapeProps }, ref) => {
    const [scrollOffset, setScrollOffset] = useState(0);
    const [selectedItem, setSelectedItem] = useState<number | null>(null);
    const listRef = useRef<any>(null);

    useEffect(() => {
      const handleScroll = (e: WheelEvent) => {
        e.preventDefault();
        setScrollOffset((prev) => {
          const newOffset = prev - e.deltaY;
          return Math.max(Math.min(newOffset, 0), -((items.length * 30) - height + 10));
        });
      };

      const container = listRef.current;
      container?.addEventListener('wheel', handleScroll);

      return () => {
        container?.removeEventListener('wheel', handleScroll);
      };
    }, [items.length, height]);

    const handleClick = (itemIndex: number) => {
      setSelectedItem(itemIndex);
      onSelected(id, itemIndex);
    };

    return (
      <Group x={x} y={y} ref={ref} {...shapeProps} clipFunc={(ctx) => {
        ctx.rect(0, 0, width, height);
      }}>
        {/* Rectángulo del listbox */}
        <Rect
          x={0}
          y={0}
          width={width}
          height={height}
          cornerRadius={10}
          stroke="black"
          strokeWidth={2}
          fill="none"
        />

        {/* Elementos de la lista con desplazamiento */}
        <Group ref={listRef} y={scrollOffset}>
          {items.map((item, index) => (
            <Group key={index} onClick={() => handleClick(index)}>
              <Rect
                x={0}
                y={30 * index}
                width={width}
                height={30}
                fill={selectedItem === index ? 'lightblue' : 'white'}
              />
              <Text
                x={10}
                y={30 * index + 5}
                text={item}
                fontFamily="Comic Sans MS, cursive"
                fontSize={20}
                fill="black"
              />
            </Group>
          ))}
        </Group>
      </Group>
    );
  }
);

export default ListBoxShape;
brauliodiez commented 1 month ago

Check comments on the PR, and ping @antonio06