Skip to content

Commit

Permalink
Merge pull request #1 from ObservedObserver/dev
Browse files Browse the repository at this point in the history
feat: date picker
  • Loading branch information
ObservedObserver authored Nov 23, 2023
2 parents 77a45b2 + 7add2e6 commit 2709828
Show file tree
Hide file tree
Showing 7 changed files with 134 additions and 95 deletions.
7 changes: 7 additions & 0 deletions pages/DatePicker.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import streamlit as st
import streamlit_shadcn_ui as ui

st.header("Date Picker")
dt = ui.date_picker(key="date_picker", label="Date Picker")

st.write("Date:", dt)
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { Button } from "@/components/ui/button";
import { Calendar } from "@/components/ui/calendar";
import {
Popover,
Expand All @@ -7,29 +8,68 @@ import {
import { forwardRef, useEffect, useState } from "react";
import { Streamlit } from "streamlit-component-lib";

function formatDate(date: Date): string {
const year = date.getFullYear();
const month = (date.getMonth() + 1).toString().padStart(2, '0'); // Months are 0-indexed
const day = date.getDate().toString().padStart(2, '0');

export const StDatePickerContent = forwardRef<HTMLDivElement>((props, ref) => {
const [date, setDate] = useState<Date>();
return `${year}-${month}-${day}`;
}

interface StDatePickerContentProps {
value?: string;
}
export const StDatePickerContent = forwardRef<
HTMLDivElement,
StDatePickerContentProps
>((props, ref) => {
const { value } = props;
const [date, setDate] = useState<Date>(new Date(value));

useEffect(() => {
setDate(new Date(value));
}, [value]);

useEffect(() => {
if (ref && typeof ref !== 'function') {
if (ref && typeof ref !== "function") {
Streamlit.setFrameHeight(ref.current.offsetHeight + 20);
}
});

return (
<Popover open={true}>
<PopoverTrigger className="hidden">
x
</PopoverTrigger>
<PopoverTrigger className="hidden"></PopoverTrigger>
<PopoverContent ref={ref} className="w-auto p-0">
<Calendar
mode="single"
selected={date}
onSelect={setDate}
initialFocus
/>
<div className="mb-4 mx-4 flex gap-2">
<Button
onClick={() => {
Streamlit.setComponentValue({
value: formatDate(date),
open: false,
});
}}
>
Pick
</Button>
<Button
variant="secondary"
onClick={() => {
Streamlit.setComponentValue({
value: value ? formatDate(new Date(value)) : value,
open: false,
});
}}
>
Cancel
</Button>
</div>
</PopoverContent>
</Popover>
);
})
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,56 +3,72 @@ import { Calendar as CalendarIcon } from "lucide-react";

import { cn, getPositionRelativeToTopDocument } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Popover,
PopoverTrigger,
} from "@/components/ui/popover";
import { forwardRef, useEffect, useRef, useState } from "react";
import { Popover, PopoverTrigger } from "@/components/ui/popover";
import { forwardRef, useEffect, useState } from "react";
import { Streamlit } from "streamlit-component-lib";
import { useBodyStyle } from "@/hooks/useBodyStyle";
import { Label } from "@/components/ui/label";

interface StDatePickerTriggerProps {
value?: string;
open: boolean;
label?: string;
}

export const StDatePickerTrigger = forwardRef<
HTMLDivElement,
StDatePickerTriggerProps
>((props, ref) => {
const { label } = props;
const [open, setOpen] = useState(Boolean(props.open));

const date = props.value ? new Date(props.value) : null;

useEffect(() => {
setOpen(Boolean(props.open));
}, [props.open]);

export const StDatePickerTrigger = forwardRef<HTMLButtonElement>((props, ref) => {
const [date, setDate] = useState<Date>();
const container = useRef(null);
const [open, setOpen] = useState(false);
useEffect(() => {
if (ref) {
Streamlit.setFrameHeight(container.current.offsetHeight + 10);
if (ref && typeof ref !== 'function') {
Streamlit.setFrameHeight(ref.current.offsetHeight + 10);
}
});
useEffect(() => {
if (container.current) {
const pos = getPositionRelativeToTopDocument(container.current);
if (ref && typeof ref !== 'function') {
const pos = getPositionRelativeToTopDocument(ref.current);

Streamlit.setComponentValue({
x: pos.left,
// consider the margin of the container
y:
pos.top +
container.current.offsetHeight +
container.current.style.marginTop.replace("px", "") * 1,
ref.current.offsetHeight +
Number(ref.current.style.marginTop.replace("px", "")),
open,
});
}
}, [open]);
useBodyStyle("body { padding-right: 0.5em !important; }")
useBodyStyle("body { padding-right: 0.5em !important; }");
console.log(label);
return (
<Popover>
<PopoverTrigger ref={container} asChild>
<Button
ref={ref}
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal m-1",
!date && "text-muted-foreground"
)}
onClick={() => {
setOpen(!open);
}}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
<PopoverTrigger asChild>
<div className="m-1" ref={ref}>
{label && <Label className="mb-2 block">{label}</Label>}
<Button
variant={"outline"}
className={cn(
"w-[280px] justify-start text-left font-normal",
!date && "text-muted-foreground"
)}
onClick={() => {
setOpen((v) => !v);
}}
>
<CalendarIcon className="mr-2 h-4 w-4" />
{date ? format(date, "PPP") : <span>Pick a date</span>}
</Button>
</div>
</PopoverTrigger>
</Popover>
);
Expand Down
1 change: 0 additions & 1 deletion streamlit_shadcn_ui/py_components/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from .select import select
from .tabs import tabs
from .card import card
from .containers import tailwind_container
from .avatar import avatar
from .date_picker import date_picker
from .table import table
Expand Down
1 change: 0 additions & 1 deletion streamlit_shadcn_ui/py_components/containers/__init__.py
Original file line number Diff line number Diff line change
@@ -1 +0,0 @@
from .tailwind import tailwind_container
43 changes: 0 additions & 43 deletions streamlit_shadcn_ui/py_components/containers/tailwind.py

This file was deleted.

45 changes: 33 additions & 12 deletions streamlit_shadcn_ui/py_components/date_picker.py
Original file line number Diff line number Diff line change
@@ -1,15 +1,22 @@
from streamlit_shadcn_ui.py_components.utils.callback import register_callback
from streamlit_shadcn_ui.py_components.utils.declare import declare_component
from streamlit_extras.stylable_container import stylable_container
from streamlit_extras.stylable_container import stylable_container

from streamlit_shadcn_ui.py_components.utils.session import init_session
import streamlit as st

def date_picker_trigger():
_RELEASE = True

def date_picker_trigger(value = None, label: str = None, open_status = False, key = None):
name = "date_picker_trigger"
_component_func = declare_component(name, release=False)
return _component_func(comp=name)
_component_func = declare_component(name, release=_RELEASE)
props = {"value": value, "open": open_status, "label": label}
return _component_func(comp=name, props=props, key=key, default={"x": 0, "y": 0, "open": False})

def date_picker_content(x: int, y: int, open: bool = False):
def date_picker_content(x: int, y: int, value=None, default_value=None, open: bool = False, key=None, on_change=None, args=None, kwargs=None):
name = "date_picker_content"
_component_func = declare_component(name, release=False)
_component_func = declare_component(name, release=_RELEASE)
register_callback(key=key, callback=on_change, args=args, kwargs=kwargs)
container = stylable_container(key="cont", css_styles=f"""
{{
position: absolute;
Expand All @@ -20,16 +27,30 @@ def date_picker_content(x: int, y: int, open: bool = False):
}}
""")
with container:
result = _component_func(comp=name)
props = {"value": value}
result = _component_func(comp=name, props=props, key=key, default={"value": default_value, "open": False})

return result

def date_picker(label=None, value=None, min_value=None, max_value=None, key=None):
def date_choosen_handler(from_key, to_key):
st.session_state[to_key]['open'] = st.session_state[from_key]['open']

def date_picker(label=None, default_value=None, key=None):
trigger_component_key = f"trigger_{key}"
content_component_key = f"content_{key}"
init_session(key=trigger_component_key, default_value={"x": 0, "y": 0, "open": False})
init_session(key=content_component_key, default_value={"value": default_value, "open": False})
open_status = st.session_state[trigger_component_key]['open']
with stylable_container(key="all", css_styles="""
{
position: relative;
}
"""):
trigger_pos = date_picker_trigger()

choice = date_picker_content(x=trigger_pos['x'], y=trigger_pos['y'], open=trigger_pos['open'])
return choice
value = st.session_state[content_component_key]['value']
trigger_pos = date_picker_trigger(value=value, label=label, open_status=open_status, key=trigger_component_key)
# need to sync value, or "cancel" will not work
st.session_state[content_component_key]['open'] = trigger_pos['open']
content_state = date_picker_content(value=value, x=trigger_pos['x'], y=trigger_pos['y'], open=open_status, key=content_component_key, on_change=date_choosen_handler, kwargs={"from_key": content_component_key, "to_key": trigger_component_key})
value = content_state['value']
return value

0 comments on commit 2709828

Please sign in to comment.