Skip to content

add CheckboxCard, CheckboxIndicator, RadioCard, RadioIndicator #486#509

Merged
AnnMarieW merged 10 commits intosnehilvj:masterfrom
deadkex:#486
Feb 15, 2025
Merged

add CheckboxCard, CheckboxIndicator, RadioCard, RadioIndicator #486#509
AnnMarieW merged 10 commits intosnehilvj:masterfrom
deadkex:#486

Conversation

@deadkex
Copy link
Copy Markdown
Contributor

@deadkex deadkex commented Feb 9, 2025

Played around with implementing CheckboxCards.
Some help would be appreciated.
See comments for examples.

@AnnMarieW
Copy link
Copy Markdown
Collaborator

AnnMarieW commented Feb 9, 2025

Hi @deadkex

This looks awesome! Thanks for the great PR 🚀

Callbacks work perfectly after fixing the typo in the id

Try changing

dmc.Box(id="output2", children="Output 1:"),

To

dmc.Box(id="output1", children="Output 1:"),

I'll take a closer look at the rest later, but just wanted you to know that the callbacks work 🙂

Comment thread src/ts/components/core/checkbox/CheckboxCard.tsx Outdated
@deadkex
Copy link
Copy Markdown
Contributor Author

deadkex commented Feb 9, 2025

grafik

import dash
from dash import Dash, Input, Output, callback

import dash_mantine_components as dmc

dash._dash_renderer._set_react_version('18.2.0')  # noqa
app = Dash(external_stylesheets=dmc.styles.ALL)

app.layout = dmc.MantineProvider(
    forceColorScheme="dark",
    children=dmc.Box([
        dmc.CheckboxGroup(
            id="cbg",
            label="CheckboxCard Group",
            children=dmc.Group([
                dmc.CheckboxCard(
                    className="checkboxcardroot",
                    children=[dmc.Stack([dmc.Text("CheckboxCard 1, no Checkbox", size="xl"),
                                         dmc.Text("description", c="dimmed")])],
                    value="1"),
                dmc.CheckboxCard(
                    className="checkboxcardroot",
                    children=dmc.Group(wrap="nowrap", align="flex-start", children=[
                        dmc.CheckboxIndicator(),
                        dmc.Stack([dmc.Text("CheckboxCard 2", size="xl"), dmc.Badge("A badge")])]),
                    value="2"),
                dmc.CheckboxCard(
                    className="customcheckboxcardroot",
                    children=[dmc.Stack([dmc.Text("Custom CheckboxCard 3", size="xl"),
                                         dmc.Text("description", c="dimmed")])],
                    value="3"),
            ],
                mt=10,
            ),
            value=["1"],
        ),
        dmc.Box(id="output1", children="Output 1:"),
        dmc.Space(h=30),

        dmc.Text("Card without styles:"),
        dmc.CheckboxCard(
            id="cb",
            withBorder=True,
            children=dmc.Group(wrap="nowrap", align="flex-start", children=[
                dmc.CheckboxIndicator(),
                dmc.Stack([dmc.Text("CheckboxCard", size="xl"), dmc.Text("description", c="dimmed")])])
        ),
        dmc.Box(id="output2", children="Output 2:"),
    ],
        pt="10px", px="20%"
    )
)


@callback(
    Output("output1", "children"),
    Input("cbg", "value")
)
def on_cbg(event):
    if event:
        return f"Output 1: {event}"
    return "nothing"


@callback(
    Output("output2", "children"),
    Input("cb", "checked")
)
def on_cb(event):
    if event:
        return f"Output 2: {event}"
    return "nothing"


if __name__ == "__main__":
    app.run(debug=True)
.checkboxcardroot {
    position: relative;
    padding: var(--mantine-spacing-md);
    transition: border-color 150ms ease;

    &[data-checked] {
        border-color: var(--mantine-primary-color-filled);
    }
}

.checkboxcardroot:hover {
    background-color: light-dark(
            var(--mantine-color-gray-0),
            var(--mantine-color-dark-6)
    );
}

.customcheckboxcardroot {
    position: relative;
    padding: var(--mantine-spacing-md);
    transition: border-color 150ms ease;

    &[data-checked] {
        border-color: var(--mantine-primary-color-filled);
    }
}

.customcheckboxcardroot::after {
    position: absolute;
    inset-block-start: 2px;
    inset-inline-end: 2px;
    width: 0;
    height: 0;
    opacity: 0;
    transition: all 0.3s cubic-bezier(0.645, 0.045, 0.355, 1);
    border-block-end: 10px solid transparent;
    border-inline-start: 10px solid transparent;
    border-start-end-radius: 6px;
    content: '';
}

.customcheckboxcardroot[data-checked="true"]::after {
        opacity: 1;
        border: 10px solid #1677ff;
        border-block-end: 10px solid transparent;
        border-inline-start: 10px solid transparent;
        border-start-end-radius: 6px;
}

@deadkex deadkex changed the title add CheckboxCard and CheckboxIndicator #486 add CheckboxCard, CheckboxIndicator, RadioCard, RadioIndicator #486 Feb 9, 2025
@deadkex
Copy link
Copy Markdown
Contributor Author

deadkex commented Feb 9, 2025

grafik

import dash
from dash import Dash, Input, Output, callback

import dash_mantine_components as dmc

dash._dash_renderer._set_react_version('18.2.0')  # noqa
app = Dash(external_stylesheets=dmc.styles.ALL)

app.layout = dmc.MantineProvider(
    forceColorScheme="dark",
    children=dmc.Box([
        dmc.RadioGroup(
            id="rg",
            label="RadioCard Group",
            children=dmc.Group([
                dmc.RadioCard(
                    className="checkboxcardroot",
                    children=dmc.Group(wrap="nowrap", align="flex-start", children=[
                        dmc.RadioIndicator(),
                        dmc.Stack([dmc.Text("RadioCard 1", size="xl"), dmc.Badge("A badge")])]),
                    value="1"),
                dmc.RadioCard(
                    className="customcheckboxcardroot",
                    children=[dmc.Stack([dmc.Text("Custom RadioCard 2", size="xl"),
                                         dmc.Text("description", c="dimmed")])],
                    value="2"),
                dmc.RadioCard(
                    className="checkboxcardroot",
                    children=dmc.Group(wrap="nowrap", align="flex-start", children=[
                        dmc.RadioIndicator(),
                        dmc.Text("RadioCard 3", size="xl")]),
                    value="3"),
            ],
                mt=10,
            ),
            value="1",
        ),
        dmc.Box(id="output1", children="Output 1:"),
        dmc.Space(h=30),
    ],
        pt="10px", px="35%"
    )
)


@callback(
    Output("output1", "children"),
    Input("rg", "value")
)
def on_rg(event):
    if event:
        return f"Output 1: {event}"
    return "nothing"


if __name__ == "__main__":
    app.run(debug=True)

@AnnMarieW
Copy link
Copy Markdown
Collaborator

AnnMarieW commented Feb 10, 2025

@deadkex

This looks great and thanks for adding the RadioCard too 🏆

To DO:

  • tests
  • changelog entry
  • alexj review
  • dmc 1.0 released

Are you familiar with Dash testing? If so, we need to add a couple simple tests for the dash props in RadioCard and CheckboxCard to test that callbacks work both individually and with the RadioGroup and CheckboxGroup. It's not necessary to add separate tests for RadioIndicator and CheckboxIndicator since they just have pass through props.

Let me know if you run into any issues and I can help with the tests.

Before merging, ideally, I'd like to have dash 3.0 and dmc 1,0 released. If this causes a long delay, let's revisit.

@deadkex
Copy link
Copy Markdown
Contributor Author

deadkex commented Feb 10, 2025

  • tests
  • changelog entry

I didn't run the tests locally, maybe they need more adjustments but i think they should work.

@AnnMarieW
Copy link
Copy Markdown
Collaborator

Hi @deadkex
Thanks for adding the test. I have a few fixes that will make all but one test pass. Would you like me to push the changes to your PR? (Or i could just post them as comments here.)

It looks like the deselectable prop with the radio card doesn't work. Do you think it's because of this line (using input instead of button?) https://github.com/snehilvj/dash-mantine-components/blob/master/src/ts/components/core/radio/RadioGroup.tsx#L43

Did you get an example working locally with the deselectable=True?

@deadkex
Copy link
Copy Markdown
Contributor Author

deadkex commented Feb 11, 2025

Would you like me to push the changes to your PR?

Yes just push them.

It looks like the deselectable prop with the radio card doesn't work. Do you think it's because of this line (using input instead of button?) https://github.com/snehilvj/dash-mantine-components/blob/master/src/ts/components/core/radio/RadioGroup.tsx#L43

Seems like it has to do with that but i'm unsure how/what to change to make it work.
I had to add RadioCardGroupContext which uses HTMLButtonElement instead of HTMLInputElement when adding RadioCards.

Did you get an example working locally with the deselectable=True?

Seems like i missed that, nope does not work.

@AnnMarieW
Copy link
Copy Markdown
Collaborator

Ok, just the deselectable radio test fails now. I'll leave that in for now in case we come up with a solution, otherwise, we can document this as a limitation.

@RenaudLN
Copy link
Copy Markdown
Contributor

RenaudLN commented Feb 11, 2025

Hey @deadkex, at @AnnMarieW's request I had a look at making the deselectable option work. There are a few changes required. The reason why is that the 'RadioCard' is not an input and therefore has no value attribute on the html side. The changes impact both radiocard and radio but it all works on my end.

radioGroupContext

interface RadioGroupContextProps {
    radioOnClick?: (val?: string) => void;
}

radioGroup

    const handleRadioClick = (val?: string) => {
        if (val === value) {
            setProps({ value: null });
        }
    };

radio

const Radio = (props: Props) => {
    const {
        setProps,
        loading_state,
        persistence,
        persisted_props,
        persistence_type,
        value,
        ...others
    } = props;

    const { radioOnClick } = React.useContext(RadioGroupContext) || {};

    return (
        <MantineRadio
            data-dash-is-loading={getLoadingState(loading_state) || undefined}
            onChange={(ev) => setProps({ checked: ev.currentTarget.checked })}
            onClick={radioOnClick ? () => radioOnClick(value) : null}
            value={value}
            {...others}
        />
    );
};

radioCard

const RadioCard = (props: Props) => {
    const {
        children,
        setProps,
        loading_state,
        persistence,
        persisted_props,
        persistence_type,
        value,
        ...others
    } = props;

    const { radioOnClick } = React.useContext(RadioGroupContext) || {};

    return (
        <MantineRadioCard
            data-dash-is-loading={getLoadingState(loading_state) || undefined}
            onClick={radioOnClick ? () => radioOnClick(value) : null}
            value={value}
            {...others}
        >
            {children}
        </MantineRadioCard>
    );
};

And remove radioCardGroupContext.tsx and update the context import in radioCard.

With this all the radio tests work for me!

@deadkex
Copy link
Copy Markdown
Contributor Author

deadkex commented Feb 12, 2025

@RenaudLN Thank you for the help, i added your proposed changes

@deadkex deadkex marked this pull request as ready for review February 12, 2025 18:36
@AnnMarieW
Copy link
Copy Markdown
Collaborator

PR for the docs: snehilvj/dmc-docs#160

Comment thread tests/test_checkbox.py Outdated
Comment thread tests/test_radiocard.py
Copy link
Copy Markdown
Collaborator

@alexcjohnson alexcjohnson left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💃 Nice work @deadkex! Just a couple of small comments on the tests, but this looks great.

@AnnMarieW AnnMarieW merged commit 4bc5efd into snehilvj:master Feb 15, 2025
@deadkex deadkex deleted the #486 branch February 16, 2025 10:28
@AnnMarieW
Copy link
Copy Markdown
Collaborator

@deadkex
Just wanted you to know this will be available in dmc 1.0.0rc2, but I'm planning on waiting until the full 1.0.0 release to promote it and add it to the docs. Thanks again for adding this feature 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants