Skip to content

Commit

Permalink
v1.1.0
Browse files Browse the repository at this point in the history
  • Loading branch information
sujan-s committed Feb 14, 2024
1 parent 7b7fc7a commit 31d8289
Show file tree
Hide file tree
Showing 23 changed files with 402 additions and 79 deletions.
1 change: 0 additions & 1 deletion .codestory.json

This file was deleted.

5 changes: 4 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
# CHANGELOG

## 1.1.0
- Add `OptionCard` component

## 1.0.3
- Rename `HRule` to `Divider`

## 1.0.2
- Rename `ExpandableContent` to `Accordion`
- Rename `ExpandableContent` to `OptionCard`

## 1.0.1
- Fix `BreadcrumbsWrapper` element type
Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<p align="center" style="color: #343a40">
<a href="https://fictoan.io"><img src="fictoan-icon.svg" alt="emotion" height="150" width="150"></a>
<a href="https://fictoan.io"><img src="fictoan-icon.svg" alt="Fictoan Framework" height="150" width="150"></a>
</p>

<h1 align="center">Fictoan-React</h1>
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "fictoan-react",
"version": "1.0.3",
"version": "1.1.0",
"private": false,
"description": "",
"type": "module",
Expand Down
4 changes: 4 additions & 0 deletions src/assets/icons/tick.svg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
2 changes: 1 addition & 1 deletion src/components/Accordion/Accordion.stories.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ const meta: Meta<typeof Accordion> = {
parameters: {
docs: {
description: {
component: "This is an Accordion.",
component: "This is an OptionCard.",
},
},
},
Expand Down
2 changes: 1 addition & 1 deletion src/components/Accordion/Accordion.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import { CommonAndHTMLProps } from "../Element/constants";
import { Element } from "../Element/Element";
import { Text } from "../Typography/Text";

import "./Accordion.css";
import "./accordion.css";

// prettier-ignore
export interface AccordionCustomProps {
Expand Down
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
3 changes: 2 additions & 1 deletion src/components/Form/RadioButton/RadioButton.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import React from "react";
import { Element } from "../../Element/Element";
import { BaseInputComponent } from "../BaseInputComponent/BaseInputComponent";

import "./radio-button.css";
import { RadioButtonProps, RadioButtonElementType } from "./constants";

import "./radio-button.css";

export const RadioButton = React.forwardRef(
({ onClick, ...props }: RadioButtonProps, ref: React.Ref<RadioButtonElementType>) => {
return (
Expand Down
79 changes: 79 additions & 0 deletions src/components/OptionCard/OptionCard.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";

import { OptionCardsGroup, OptionCard } from "./OptionCard";
import { Text } from "../Typography/Text";

const meta: Meta<typeof OptionCardsGroup> = {
title: 'Components/OptionCardsGroup',
component: OptionCardsGroup,
tags: ["autodocs"],
args: {
allowMultipleSelections: false,
showTickIcon: true,
},
parameters: {
docs: {
description: {
component: "A group of option cards allowing for single or multiple selections.",
},
},
},
};

export default meta;
type Story = StoryObj<typeof OptionCardsGroup>;

export const SingleSelection: Story = {
render: (args) => (
<OptionCardsGroup {...args}>
<OptionCard id="option1" padding="micro">
<Text>Option 1</Text>
</OptionCard>
<OptionCard id="option2" padding="micro">
<Text>Option 2</Text>
</OptionCard>
<OptionCard id="option3" padding="micro">
<Text>Option 3</Text>
</OptionCard>
</OptionCardsGroup>
),
args: {
allowMultipleSelections: false,
showTickIcon: true,
},
parameters: {
docs: {
description: {
story: "A group of option cards configured for single selection.",
},
},
},
};

export const MultipleSelection: Story = {
render: (args) => (
<OptionCardsGroup {...args}>
<OptionCard id="option1" padding="micro">
<Text>Option 1</Text>
</OptionCard>
<OptionCard id="option2" padding="micro">
<Text>Option 2</Text>
</OptionCard>
<OptionCard id="option3" padding="micro">
<Text>Option 3</Text>
</OptionCard>
</OptionCardsGroup>
),
args: {
allowMultipleSelections: true,
showTickIcon: true,
},
parameters: {
docs: {
description: {
story: "A group of option cards configured for multiple selections.",
},
},
},
};
136 changes: 136 additions & 0 deletions src/components/OptionCard/OptionCard.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,136 @@
import React, { createContext, useContext, useState, ReactNode, useCallback } from "react";

import { Element } from "../Element/Element";
import { Card, CardElementType, CardProps } from "../Card/Card";

import "./option-card.css";

// import TickIcon from "../../assets/icons/tick.svg";

export interface OptionCardsProviderProps {
children : ReactNode;
allowMultipleSelections ? : boolean;
showTickIcon ? : boolean;
onSelectionChange ? : (selectedIds: Set<string>) => void;
}

export interface OptionCardProps extends CardProps {
id : string;
children : ReactNode;
disabled ? : boolean;
}

const OptionCardsContext = createContext<{
isSelected : (id: string) => boolean;
toggleSelection : (id: string) => void;
showTickIcon ? : boolean;
}>({
isSelected : () => false,
toggleSelection : () => {},
showTickIcon : false,
});

export const OptionCardsGroup: React.FC<OptionCardsProviderProps> = (
{
children,
allowMultipleSelections = false,
showTickIcon,
onSelectionChange,
...props
},
) => {
const [ selectedIds, setSelectedIds ] = useState<Set<string>>(new Set());

const toggleSelection = useCallback((id: string) => {
setSelectedIds(prevSelectedIds => {
const newSelectedIds = new Set(prevSelectedIds);
if (allowMultipleSelections) {
if (newSelectedIds.has(id)) {
newSelectedIds.delete(id);
} else {
newSelectedIds.add(id);
}
} else {
if (newSelectedIds.has(id) && prevSelectedIds.size === 1) {
newSelectedIds.clear();
} else {
newSelectedIds.clear();
newSelectedIds.add(id);
}
}
onSelectionChange?.(newSelectedIds);
return newSelectedIds;
});
}, [ allowMultipleSelections, onSelectionChange ]);

const isSelected = useCallback((id: string) => {
return selectedIds.has(id);
}, [ selectedIds ]);

return (
<OptionCardsContext.Provider value={{ isSelected, toggleSelection, showTickIcon }}>
{children}
</OptionCardsContext.Provider>
);
};

export const useOptionCard = (id: string) => {
const context = useContext(OptionCardsContext);
return {
isSelected : context.isSelected(id),
toggleSelection : () => context.toggleSelection(id),
showTickIcon : context.showTickIcon,
};
};

export const OptionCard: React.FC<OptionCardProps> = ({ id, children, disabled = false, ...props }) => {
const { isSelected, toggleSelection, showTickIcon } = useOptionCard(id);

let classNames = [];

if (isSelected) {
classNames.push("selected");
}

if (disabled) {
classNames.push("disabled");
}

const handleKeyDown = (e: React.KeyboardEvent) => {
if ((e.key === "Enter" || e.key === " ") && !disabled) {
e.preventDefault();
toggleSelection();
}
};

const handleClick = (e: React.MouseEvent<HTMLDivElement, MouseEvent>) => {
if (!disabled) {
toggleSelection();
props.onClick?.(e);
}
};

return (
<Element<CardElementType>
as={Card}
data-option-card
role="button"
tabIndex={disabled ? -1 : 0}
aria-disabled={disabled ? "true" : "false"}
aria-selected={isSelected ? "true" : "false"}
classNames={classNames}
onClick={handleClick}
onKeyDown={handleKeyDown}
{...props}
>
{showTickIcon
? (
<svg viewBox="0 0 24 24" className="tick-icon">
<circle cx="12" cy="12" r="11" />
<path d="M8 13 L11 15 L16 9" />
</svg>
) : null}
{children}
</Element>
);
};
2 changes: 2 additions & 0 deletions src/components/OptionCard/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
export { OptionCard, OptionCardsGroup, type OptionCardProps } from "./OptionCard";
export { useOptionCard } from "./OptionCard";
72 changes: 72 additions & 0 deletions src/components/OptionCard/option-card.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
[data-option-cards-group] {
width : 100%;
}

[data-option-card] {
user-select : none;
transition : all 0.2s;

&.disabled {
filter : grayscale(24%);
opacity : 0.6;
cursor : not-allowed;
}

&:not(.disabled) { cursor : pointer; }

&:hover {
z-index : 500;
border : var(--option-card-border-width) solid var(--option-card-border-hover);
background : var(--option-card-bg-hover);
box-shadow : 0 1px 2px rgba(0, 0, 0, 0.04), 0 12px 24px rgba(0, 0, 0, 0.08);

.tick-icon {
display : block;

circle { fill : var(--option-card-tick-bg-hover); }
path { stroke : var(--option-card-tick-line-hover); }
}
}

&:active { box-shadow : none; }

&.selected {
background-color : var(--option-card-bg-selected);
border : var(--option-card-border-width) solid var(--option-card-border-selected);

.tick-icon {
display : block;
}
}

/* Here’s a clever bit of UX */
&.selected:hover {
.tick-icon circle { fill : var(--option-card-tick-bg-selected); }
.tick-icon path {stroke : var(--option-card-tick-line-selected); }
}

&:focus {
outline : 2px solid var(--option-card-border-hover);
outline-color : var(--radio-tabs-label-focus-border);
}

/* THE TICK ============================================================= */
.tick-icon {
display : none;
position : absolute;
top : 8px;
right : 8px;
width : 24px;
height : 24px;
transition : all 0.2s;
}

.tick-icon circle { fill : var(--option-card-tick-bg-selected); }

.tick-icon path {
stroke : var(--option-card-tick-line-selected);
stroke-width : 2px;
fill : none;
stroke-linecap : round;
}
}
5 changes: 3 additions & 2 deletions src/components/Table/TablePagination/TablePagination.tsx
Original file line number Diff line number Diff line change
@@ -1,18 +1,19 @@
/// <reference types="vite-plugin-svgr/client" />
import React from "react";

import { CommonAndHTMLProps } from "../../Element/constants";
import { Div, Element } from "../../Element";
import { Heading } from "../../Typography";
import { Text } from "../../Typography";
import { Spinner } from "../../Spinner";

import { CommonAndHTMLProps } from "../../Element/constants";

import "./table-pagination.css";

import PreviousButton from "../../../assets/icons/left.svg?react";
import NextButton from "../../../assets/icons/right.svg?react";
import FirstPageButton from "../../../assets/icons/left.svg?react";

import { Spinner } from "../../Spinner";

// prettier-ignore
export interface TablePaginationCustomProps {
Expand Down
2 changes: 1 addition & 1 deletion src/components/Tabs/Tabs.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React, { useEffect } from "react";

import { Element } from "../Element/Element";
import { Div } from "../Element/Tags";
import { Divider } from "../HRule/Divider";
import { Divider } from "../Divider/Divider";
import { Text } from "../Typography/Text";

import { CommonAndHTMLProps } from "../Element/constants";
Expand Down
Loading

0 comments on commit 31d8289

Please sign in to comment.