Skip to content

Commit

Permalink
feat: add tabs component
Browse files Browse the repository at this point in the history
  • Loading branch information
abelflopes committed Dec 30, 2023
1 parent 3109ee2 commit 0ba9dd8
Show file tree
Hide file tree
Showing 17 changed files with 286 additions and 1 deletion.
2 changes: 1 addition & 1 deletion docs/ROADMAP.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ This roadmap is our plan for building a complete front-end framework. Each item

[ ] **Badge:** Small signs that show if something is good or special.

[ ] **Tabs:** Sections to organize our site, like folders.
[x] **Tabs:** Sections to organize our site, like folders.

[x] **Alert:** A notice to tell users important things.

Expand Down
25 changes: 25 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

32 changes: 32 additions & 0 deletions packages/components/tabs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# RCK | React Tabs Component

> :warning: **WARNING**: This component library is being updated frequently and it's currently unstable due to being in it's early stages, we advice you to use only in production environments only after version **2.0.0**.
for React applications. It's a powerhouse for developers, providing an efficient way to organize and showcase content while ensuring a streamlined user experience.

With the Tabs component, developers can effortlessly create expandable sections of content, allowing users to toggle open or closed sections to focus on specific details. This functionality is a game-changer for handling extensive text, intricate descriptions, or any additional information without cluttering the user interface

### Installation

To integrate the this component into your React apps, you can install it using npm or yarn: `npm i --save @react-ck/tabs` or `yarn add @react-ck/tabs`.

You will also need to set up the manager, install it using npm or yarn: `npm i --save @react-ck/manager` or `yarn add @react-ck/manager`.

Wrap your app root with the theme provider and use this component:

```tsx
import { Manager } from "@react-ck/manager";
import { Tabs } from "@react-ck/tabs";

const myApp = () => (
<Manager>
<Tabs ... />
</Manager>
);
```

<!-- storybook-ignore -->

---

Check the documentation website - [react-ck.js.org](https://react-ck.js.org).
3 changes: 3 additions & 0 deletions packages/components/tabs/babel.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
extends: "@react-ck/babel-config",
};
4 changes: 4 additions & 0 deletions packages/components/tabs/declarations.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
declare module "*.module.scss" {
const content: Record<string, string>;
export default content;
}
2 changes: 2 additions & 0 deletions packages/components/tabs/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
// Hack for module resolution of non built packages
export * from "./src/index";
1 change: 1 addition & 0 deletions packages/components/tabs/jest.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export { config as default } from "@react-ck/jest-config";
39 changes: 39 additions & 0 deletions packages/components/tabs/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
{
"name": "@react-ck/tabs",
"private": false,
"version": "1.0.0",
"main": "dist/index.js",
"types": "dist/index.d.ts",
"files": [
"/dist"
],
"homepage": "https://github.com/abelflopes/react-ck/tree/master/packages/components/tabs#readme",
"repository": {
"type": "git",
"url": "git+https://github.com/abelflopes/react-ck.git"
},
"scripts": {
"build": "NODE_ENV=production webpack",
"lint:typescript": "tsc --noEmit",
"test": "npx -y npm-run-all -s test:*",
"test:unit": "jest --testPathPattern=\".unit.*\"",
"test:snapshot": "jest --testPathPattern=\".snapshot.*\"",
"test:snapshot:update": "jest --testPathPattern=\".snapshot.*\" -u"
},
"devDependencies": {
"@react-ck/babel-config": "^1.0.0",
"@react-ck/jest-config": "^1.0.0",
"@react-ck/typescript-config": "^1.0.0",
"@react-ck/webpack-config": "^1.0.0",
"@types/react": "^18.2.33"
},
"dependencies": {
"@react-ck/scss-utils": "^1.1.2",
"@react-ck/text": "^1.2.6",
"@react-ck/theme": "^1.4.1",
"classnames": "^2.3.2"
},
"peerDependencies": {
"react": "^18.2.0"
}
}
19 changes: 19 additions & 0 deletions packages/components/tabs/specs/index.snapshot.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from "react";
import { Tabs } from "../src/index";
import renderer from "react-test-renderer";

describe("Snapshot Tabs", () => {
test("renders correctly", async () => {
const tree = renderer
.create(
<Tabs
items={[
{ heading: "item 1", content: "content 1" },
{ heading: "item 2", content: "content 2" },
]}
/>,
)
.toJSON();
expect(tree).toMatchSnapshot();
});
});
24 changes: 24 additions & 0 deletions packages/components/tabs/specs/index.unit.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from "react";
import { Tabs } from "../src/index";
import { render, screen } from "@testing-library/react";
import "@testing-library/jest-dom";

describe("Unit Tabs", () => {
test("renders correctly", async () => {
const items = [
{ heading: "item 1", content: "content 1" },
{ heading: "item 2", content: "content 2" },
];

render(<Tabs items={items} />);

await Promise.all(
items.map(async (item) => {
const header = await screen.findByText(item.heading);
const children = await screen.findByText(item.content);
expect(header).toBeInTheDocument();
expect(children).toBeInTheDocument();
}),
);
});
});
47 changes: 47 additions & 0 deletions packages/components/tabs/src/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import React, { useState } from "react";
import classNames from "classnames";
import styles from "./styles/index.module.scss";

export interface TabsItem {
heading: NonNullable<React.ReactNode>;
content: NonNullable<React.ReactNode>;
}

export interface TabsProps extends Omit<React.HTMLAttributes<HTMLDivElement>, "children"> {
items: TabsItem[];
}

/**
* Tabs is a way to navigate between multiple views of information. It’s used to fit more information in a smaller area.
* @param props - {@link TabsProps}
* @returns a React element
*/

export const Tabs = ({
items,
className,
...otherProps
}: Readonly<TabsProps>): React.ReactElement => {
const [current, setCurrent] = useState(0);

return (
<div className={classNames(className, styles.root)} {...otherProps}>
<div className={styles.track}>
{items.map(({ heading }, key) => (
<button
key={JSON.stringify(heading)}
className={classNames(styles.tab, {
[`${styles.tab_active}`]: key === current,
})}
onClick={() => {
setCurrent(key);
}}>
{heading}
</button>
))}
</div>

{items[current]?.content}
</div>
);
};
29 changes: 29 additions & 0 deletions packages/components/tabs/src/styles/index.module.scss
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
@import "@react-ck/theme";
@import "@react-ck/scss-utils";
@import "@react-ck/text";

.root {
display: flex;
flex-direction: column;
gap: get-spacing(1);
}

.track {
display: flex;
flex-wrap: wrap;
}

.tab {
@include define-css-var(tabs, border-color, get-color(neutral-200));
@include text-base;

background: transparent;
padding: get-spacing(1) get-spacing(2);
border: none;
cursor: pointer;
border-bottom: solid #{get-css-var(spacing, border)} #{get-css-var(tabs, border-color)};
}

.tab_active {
@include define-css-var(tabs, border-color, get-color(highlight-primary));
}
7 changes: 7 additions & 0 deletions packages/components/tabs/tsconfig.build.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"extends": "@react-ck/typescript-config/tsconfig.build.json",
"compilerOptions": {
"outDir": "./dist"
},
"include": ["./*.d.ts", "./src/**/*", "./src/index.ts*"]
}
4 changes: 4 additions & 0 deletions packages/components/tabs/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"extends": "@react-ck/typescript-config/tsconfig.json",
"include": ["./**/*"]
}
6 changes: 6 additions & 0 deletions packages/components/tabs/webpack.config.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { getWebpackConfig } from "@react-ck/webpack-config";
import packageJson from "./package.json";

export default getWebpackConfig({
cssHashSalt: packageJson.name,
});
1 change: 1 addition & 0 deletions packages/docs/stories/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
"@react-ck/textarea": "^1.0.0",
"@react-ck/manager": "^1.0.0",
"@react-ck/layers": "^1.0.0",
"@react-ck/tabs": "^1.0.0",
"@react-ck/theme": "^1.0.0",
"@react-ck/progress": "^1.0.0"
}
Expand Down
42 changes: 42 additions & 0 deletions packages/docs/stories/src/tabs.stories.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import React from "react";
import type { Meta, StoryObj } from "@storybook/react";
import { Manager } from "@react-ck/manager";
import { faker } from "@faker-js/faker";
import { Text } from "@react-ck/text/src";
import { configureStory } from "@react-ck/story-config";
import { Tabs } from "@react-ck/tabs/src";

type Story = StoryObj<typeof Tabs>;

const meta: Meta<typeof Tabs> = {
title: "Generic/Tabs",
...configureStory(Tabs, {
decorators: [
(Story) => (
<Manager>
<Story />
</Manager>
),
],
}),
};

export default meta;

export const Component: Story = {
parameters: {
layout: "padded",
},
args: {
items: [
{
heading: faker.lorem.word(),
content: <Text>{faker.lorem.sentence(50)}</Text>,
},
{
heading: faker.lorem.word(),
content: <Text>{faker.lorem.sentence(100)}</Text>,
},
],
},
};

0 comments on commit 0ba9dd8

Please sign in to comment.