├── src ├── components │ ├── Shared │ │ └── Enums.ts │ ├── Label │ │ ├── label.css │ │ ├── Label.stories.ts │ │ ├── RLabel.vue │ │ └── Label.mdx │ ├── TabItem │ │ ├── common.ts │ │ ├── TabItem.spec.ts │ │ ├── tab-item.css │ │ ├── TabItem.stories.ts │ │ └── TabItem.mdx │ ├── Tabs │ │ ├── types.ts │ │ ├── tabs.css │ │ ├── Tabs.spec.ts │ │ ├── RTabs.vue │ │ └── Tabs.mdx │ ├── ItemGroup │ │ ├── __snapshots__ │ │ │ └── itemgroup.spec.ts.snap │ │ ├── itemgroup.spec.ts │ │ ├── RItem.vue │ │ ├── ItemGroup.stories.ts │ │ └── RItemGroup.vue │ ├── Pagination │ │ ├── pagination.spec.ts │ │ ├── Pagination.stories.ts │ │ └── pagination.css │ ├── ProgressBar │ │ ├── progressbar.spec.ts │ │ ├── progressbar.css │ │ ├── RProgressbar.vue │ │ └── Progressbar.stories.ts │ ├── Flex │ │ ├── Flex.mdx │ │ ├── Flex.stories.js │ │ └── RFlex.vue │ ├── Grid │ │ ├── Grid.mdx │ │ └── Grid.stories.js │ ├── Box │ │ ├── Box.mdx │ │ ├── Box.stories.ts │ │ └── RBox.vue │ ├── Badge │ │ ├── badge.spec.ts │ │ ├── badge.css │ │ ├── Badge.stories.ts │ │ └── RBadge.vue │ ├── Breadcrumb │ │ ├── breadcrumb.css │ │ ├── RBreadcrumb.vue │ │ └── Breadcrumb.stories.ts │ ├── Sidebar │ │ ├── Sidebar.stories.ts │ │ ├── sidebar.spec.ts │ │ ├── sidebar.css │ │ ├── RSidebar.vue │ │ └── Sidebar.mdx │ ├── Icon │ │ ├── icon.spec.ts │ │ ├── Icon.stories.ts │ │ ├── RIcon.vue │ │ └── Icon.mdx │ ├── Modal │ │ ├── modal.spec.ts │ │ ├── modal.css │ │ └── Modal.mdx │ ├── Button │ │ ├── Button.spec.ts │ │ └── Button.stories.ts │ ├── Tooltip │ │ ├── tooltip.css │ │ ├── tooltip.spec.ts │ │ └── popper.ts │ ├── Alert │ │ ├── alert.spec.ts │ │ ├── Alert.stories.ts │ │ ├── alert.css │ │ └── RAlert.vue │ ├── Switch │ │ ├── switch.spec.ts │ │ ├── Switch.stories.ts │ │ └── switch.css │ ├── Radio │ │ ├── Radio.stories.ts │ │ ├── radio.css │ │ └── RRadio.vue │ ├── Chips │ │ ├── chip.spec.ts │ │ ├── chip.css │ │ └── Chip.stories.ts │ ├── TextArea │ │ ├── TextArea.stories.ts │ │ ├── textarea.spec.ts │ │ ├── textarea.css │ │ └── TextArea.mdx │ ├── Textfield │ │ ├── textfield.spec.ts │ │ ├── textfield.css │ │ └── Textfield.stories.ts │ ├── Avatar │ │ ├── avatar.spec.ts │ │ ├── Avatar.stories.ts │ │ ├── avatar.css │ │ ├── RAvatar.vue │ │ └── Avatar.mdx │ ├── Checkbox │ │ ├── Checkbox.stories.ts │ │ ├── checkbox.spec.ts │ │ └── checkbox.css │ ├── Accordion │ │ ├── accordion.css │ │ ├── RAccordion.vue │ │ ├── Accordion.mdx │ │ └── accordion.spec.ts │ ├── Snackbar │ │ ├── snackbar.spec.ts │ │ ├── Snackbar.stories.ts │ │ ├── snackbar.css │ │ └── RSnackbar.vue │ ├── Typography │ │ ├── Typography.mdx │ │ └── typography.css │ └── Dropdown │ │ ├── dropdown.spec.ts │ │ └── Dropdown.stories.ts ├── main.ts ├── utils │ └── helpers.ts ├── shims.d.ts ├── assets │ ├── logo.svg │ └── blank-avatar.svg ├── directives │ └── index.ts ├── scripts │ └── buildIcons.js ├── lib │ └── main.ts └── App.vue ├── .storybook ├── preview-head.html ├── manager.js ├── manager-head.html ├── Theme.js ├── main.ts ├── preview.ts ├── source-panel │ └── manager.js └── withSource.js ├── public └── favicon.ico ├── .husky └── pre-commit ├── .gitattributes ├── .editorconfig ├── shims-vue.d.ts ├── .prettierrc.cjs ├── catalog-info.yaml ├── vitest.config.ts ├── postcss.config.cjs ├── .vscode ├── extensions.json └── settings.json ├── index.html ├── tailwind.config.cjs ├── .gitignore ├── .github └── workflows │ ├── chromatic.yml │ └── publish-storybook.yml ├── tsconfig.json ├── LICENSE ├── vite.config.ts └── .eslintrc.cjs /src/components/Shared/Enums.ts: -------------------------------------------------------------------------------- 1 | export {} 2 | -------------------------------------------------------------------------------- /.storybook/preview-head.html: -------------------------------------------------------------------------------- 1 | 4 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Teknasyon/rocket-ui/HEAD/public/favicon.ico -------------------------------------------------------------------------------- /.husky/pre-commit: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env sh 2 | . "$(dirname -- "$0")/_/husky.sh" 3 | 4 | npx lint-staged 5 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | *.html linguist-detectable=false 2 | *.mdx linguist-detectable=false 3 | /.yarn/releases/** binary 4 | /.yarn/plugins/** binary -------------------------------------------------------------------------------- /src/components/Label/label.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-label { 4 | @apply text-[var(--neutral-900)] text-sm p-0 mb-2; 5 | } -------------------------------------------------------------------------------- /src/main.ts: -------------------------------------------------------------------------------- 1 | import { createApp } from 'vue'; 2 | import App from './App.vue'; 3 | import './index.css'; 4 | 5 | createApp(App).mount('#app'); 6 | -------------------------------------------------------------------------------- /.storybook/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons'; 2 | import Theme from './Theme'; 3 | 4 | addons.setConfig({ 5 | theme: Theme, 6 | panelPosition: 'right', 7 | }); 8 | 9 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | end_of_line = lf 5 | insert_final_newline = true 6 | 7 | [*.{js,json,yml}] 8 | charset = utf-8 9 | indent_style = space 10 | indent_size = 2 11 | -------------------------------------------------------------------------------- /src/components/TabItem/common.ts: -------------------------------------------------------------------------------- 1 | export type TabItemVariant = 'default' | 'text' | 'icon' 2 | export enum TabItemVariants { 3 | DEFAULT = 'default', 4 | TEXT = 'text', 5 | ICON = 'icon', 6 | } 7 | -------------------------------------------------------------------------------- /shims-vue.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue' 3 | const component: DefineComponent, Record, any> 4 | export default component 5 | } -------------------------------------------------------------------------------- /src/utils/helpers.ts: -------------------------------------------------------------------------------- 1 | export function createGuid() { 2 | function S4() { 3 | return (((1 + Math.random()) * 0x10000) | 0).toString(16).substring(1) 4 | } 5 | return `${S4() + S4()}-${S4()}-${S4()}-${S4()}-${S4()}${S4()}${S4()}` 6 | } 7 | -------------------------------------------------------------------------------- /.prettierrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | singleQuote: true, 3 | trailingComma: 'es5', 4 | printWidth: 80, 5 | tabWidth: 2, 6 | semi: true, 7 | bracketSpacing: true, 8 | arrowParens: 'always', 9 | vueIndentScriptAndStyle: false, 10 | }; 11 | -------------------------------------------------------------------------------- /src/shims.d.ts: -------------------------------------------------------------------------------- 1 | declare module '*.vue' { 2 | import type { DefineComponent } from 'vue'; 3 | const component: DefineComponent< 4 | Record, 5 | Record, 6 | any 7 | >; 8 | export default component; 9 | } 10 | -------------------------------------------------------------------------------- /catalog-info.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: backstage.io/v1alpha1 2 | kind: Component 3 | metadata: 4 | name: rocket-ui 5 | annotations: 6 | github.com/project-slug: Teknasyon/rocket-ui 7 | spec: 8 | type: other 9 | lifecycle: unknown 10 | owner: teknasyon-tech 11 | -------------------------------------------------------------------------------- /.storybook/manager-head.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /vitest.config.ts: -------------------------------------------------------------------------------- 1 | /// 2 | 3 | import { defineConfig } from 'vite'; 4 | import Vue from '@vitejs/plugin-vue'; 5 | 6 | export default defineConfig({ 7 | plugins: [Vue()], 8 | test: { 9 | globals: true, 10 | environment: 'jsdom', 11 | }, 12 | }); 13 | -------------------------------------------------------------------------------- /src/components/Tabs/types.ts: -------------------------------------------------------------------------------- 1 | import type { TabItemVariant } from '../TabItem/common' 2 | 3 | export interface Tab { 4 | id: string | number 5 | variant?: TabItemVariant 6 | label?: string 7 | prependIcon?: string 8 | appendIcon?: string 9 | disabled?: boolean 10 | active?: boolean 11 | } 12 | -------------------------------------------------------------------------------- /postcss.config.cjs: -------------------------------------------------------------------------------- 1 | /* eslint-env node */ 2 | module.exports = { 3 | plugins: { 4 | 'postcss-import': {}, 5 | 'tailwindcss/nesting': 'postcss-nested', 6 | 'tailwindcss': { config: './tailwind.config.cjs' }, 7 | 'autoprefixer': {}, 8 | 'cssnano': { preset: 'default' }, 9 | }, 10 | }; 11 | -------------------------------------------------------------------------------- /src/assets/logo.svg: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /.vscode/extensions.json: -------------------------------------------------------------------------------- 1 | { 2 | "recommendations": [ 3 | "Vue.volar", 4 | "Vue.vscode-typescript-vue-plugin", 5 | "streetsidesoftware.code-spell-checker", 6 | "dbaeumer.vscode-eslint", 7 | "esbenp.prettier-vscode", 8 | "lukas-tr.materialdesignicons-intellisense", 9 | "bradlc.vscode-tailwindcss" 10 | ] 11 | } 12 | -------------------------------------------------------------------------------- /src/components/ItemGroup/__snapshots__/itemgroup.spec.ts.snap: -------------------------------------------------------------------------------- 1 | // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html 2 | 3 | exports[`ItemGroup > should render correctly 1`] = ` 4 | "
5 |
Item
6 |
" 7 | `; 8 | 9 | exports[`RItemGroup > should render correctly 1`] = ` 10 | "
11 |
Item
12 |
" 13 | `; 14 | -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Vite App 8 | 9 | 10 |
11 | 12 | 13 | 14 | -------------------------------------------------------------------------------- /src/components/Tabs/tabs.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-tabs { 4 | @apply w-full flex flex-col transition-all duration-300 relative; 5 | 6 | &:not(.r-tabs--tile)::after { 7 | @apply block w-full h-[1px] bg-[var(--neutral-75)] -bottom-0 absolute -z-10; 8 | content: ''; 9 | } 10 | 11 | &--tile { 12 | @apply w-fit bg-[var(--primary-50)] rounded-lg p-1; 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /src/components/Pagination/pagination.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Pagination from './RPagination.vue' 5 | 6 | describe('Pagination', () => { 7 | it('should render correctly', () => { 8 | const wrapper = mount(Pagination, { 9 | props: { 10 | page: 1, 11 | totalPages: 10, 12 | }, 13 | }) 14 | 15 | expect(wrapper.find('.r-pagination').exists()).toBe(true) 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /tailwind.config.cjs: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | const defaultTheme = require('tailwindcss/defaultTheme'); 3 | /* eslint-env node */ 4 | module.exports = { 5 | mode: 'jit', 6 | content: ['./src/**/*.{vue,js,ts,jsx,tsx}'], 7 | theme: { 8 | extend: { 9 | fontFamily: { 10 | sans: ['Albert Sans', ...defaultTheme.fontFamily.sans], 11 | serif: ['Roboto', ...defaultTheme.fontFamily.serif], 12 | }, 13 | }, 14 | }, 15 | plugins: [], 16 | }; 17 | -------------------------------------------------------------------------------- /src/directives/index.ts: -------------------------------------------------------------------------------- 1 | import { type App, type DirectiveBinding, createApp, h, ref } from 'vue' 2 | import Tooltip from '../components/Tooltip/RTooltip.vue' 3 | 4 | export const vTooltip = { 5 | mounted(el: HTMLElement, binding: DirectiveBinding) { 6 | const app: App = createApp({ 7 | setup() { 8 | const props = ref(binding.value) 9 | 10 | return () => 11 | h(Tooltip, { 12 | ...props.value, 13 | }) 14 | }, 15 | }) 16 | app.mount(el) 17 | }, 18 | } 19 | -------------------------------------------------------------------------------- /src/components/ProgressBar/progressbar.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import RProgressbar from './RProgressbar.vue' 5 | 6 | describe('progressbar', () => { 7 | it('should render correctly', () => { 8 | const wrapper = mount(RProgressbar, { 9 | props: { 10 | value: 50, 11 | }, 12 | }) 13 | 14 | expect(wrapper.find('.r-progressbar').exists()).toBe(true) 15 | expect(wrapper.attributes('value')).toBe('50') 16 | }) 17 | }) 18 | -------------------------------------------------------------------------------- /.storybook/Theme.js: -------------------------------------------------------------------------------- 1 | import { create } from '@storybook/theming'; 2 | 3 | export default create({ 4 | fontBase: 5 | 'Montserrat, "Helvetica Neue", Helvetica, Arial, Tahoma, sans-serif', 6 | fontCode: 7 | 'Menlo, Monaco, Consolas, "Liberation Mono", "Courier New", monospace', 8 | 9 | colorPrimary: '#6c757d', 10 | colorSecondary: '#007bff', 11 | 12 | base: 'light', 13 | brandTitle: 'Rocket UI Vue', 14 | brandUrl: '/?path=/docs/getting-started--documentation', 15 | brandTarget: '_self', 16 | // brandImage: require('./logo.svg'), 17 | }); 18 | -------------------------------------------------------------------------------- /src/assets/blank-avatar.svg: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /src/components/Flex/Flex.mdx: -------------------------------------------------------------------------------- 1 | import { Canvas, Meta, Story, Controls } from '@storybook/blocks'; 2 | import * as FlexStories from './Flex.stories'; 3 | 4 | 5 | 6 | # Flex 7 | 8 | Flex is Box with display set to flex and comes with helpful style shorthand. It renders a `div` element. 9 | 10 | ### Overview 11 | 12 | 13 | 14 | 15 | 16 | ### Playground 17 | 18 | > Changes you make in the controls will be reflected in the example above. 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/Grid/Grid.mdx: -------------------------------------------------------------------------------- 1 | import { Canvas, Meta, Story, Controls } from '@storybook/blocks'; 2 | import * as GridStories from './Grid.stories'; 3 | 4 | 5 | 6 | # Grid 7 | 8 | Grid is a simple box component that can be used to create layouts. 9 | 10 | > A simple box Components 11 | 12 | 13 | 14 | 15 | 16 | ### Playground 17 | 18 | > Changes you make in the controls will be reflected in the example above. 19 | 20 | 21 | -------------------------------------------------------------------------------- /.storybook/main.ts: -------------------------------------------------------------------------------- 1 | import type { StorybookConfig } from '@storybook/vue3-vite'; 2 | const config: StorybookConfig = { 3 | stories: ['../src/**/*.mdx', '../src/**/*.stories.@(js|jsx|ts|tsx)'], 4 | addons: [ 5 | '@storybook/addon-links', 6 | '@storybook/addon-essentials', 7 | '@storybook/addon-interactions', 8 | '@storybook/addon-mdx-gfm', 9 | '@storybook/addon-actions', 10 | ], 11 | framework: { 12 | name: '@storybook/vue3-vite', 13 | options: {}, 14 | }, 15 | docs: { 16 | autodocs: 'tag', 17 | defaultName: 'Documentation', 18 | }, 19 | }; 20 | export default config; 21 | -------------------------------------------------------------------------------- /src/components/Box/Box.mdx: -------------------------------------------------------------------------------- 1 | import { Canvas, Meta, Story, Controls } from '@storybook/blocks'; 2 | import * as BoxStories from './Box.stories'; 3 | 4 | 5 | 6 | # Box 7 | 8 | Box is the most abstract component on top of which all other Rocket UI components are built. By default, it renders a `div` element 9 | 10 | > A simple box Components 11 | 12 | 13 | 14 | 15 | 16 | ### Playground 17 | 18 | > Changes you make in the controls will be reflected in the example above. 19 | 20 | 21 | -------------------------------------------------------------------------------- /src/components/Badge/badge.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Badge from './RBadge.vue' 5 | 6 | describe('Badge', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(Badge, { 9 | props: { 10 | placement: 'right', 11 | size: 'small', 12 | content: '1', 13 | }, 14 | }) 15 | expect(wrapper.element.classList.contains('badge')) 16 | expect(wrapper.element.classList.contains('badge--right')) 17 | expect(wrapper.element.classList.contains('badge--small')) 18 | expect(wrapper.element.innerHTML.includes('1')) 19 | }) 20 | }) 21 | -------------------------------------------------------------------------------- /src/components/ProgressBar/progressbar.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-progressbar { 4 | @apply appearance-none border-none rounded-full overflow-hidden w-full; 5 | 6 | &[value]::-webkit-progress-value { 7 | background-color: var(--primary-500); 8 | transition: inline-size 0.25s ease-out; 9 | } 10 | 11 | &[value]::-moz-progress-bar { 12 | background-color: var(--primary-500); 13 | transition: inline-size 0.25s ease-out; 14 | } 15 | 16 | &[value]::-ms-fill { 17 | background-color: var(--primary-500); 18 | transition: inline-size 0.25s ease-out; 19 | } 20 | 21 | &[value]::-webkit-progress-bar { 22 | background-color: var(--primary-200); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /src/scripts/buildIcons.js: -------------------------------------------------------------------------------- 1 | import util from '@mdi/util'; 2 | 3 | const meta = util.getMeta(true); 4 | // eslint-disable-next-line 5 | const find = /(\-\w)/g; 6 | // eslint-disable-next-line @typescript-eslint/no-unused-vars 7 | const convert = function ([_, matches]) { 8 | return matches.toUpperCase(); 9 | }; 10 | 11 | const lines = meta.map((icon) => { 12 | let name = icon.name.replace(find, convert); 13 | name = `${name[0].toUpperCase()}${name.slice(1)}`; 14 | 15 | return `mdi${name} : "${icon.path}"`; 16 | }); 17 | 18 | const outputTS = `// Material Design Icons v${util.getVersion()}\n 19 | const svg = 20 | {\n${lines.join(',\n')}\n}\nexport default svg`; 21 | 22 | util.write('src/assets/icons/mdi.js', outputTS); 23 | -------------------------------------------------------------------------------- /src/components/Breadcrumb/breadcrumb.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-breadcrumb { 4 | @apply flex items-center; 5 | 6 | &__item { 7 | @apply flex items-center cursor-pointer; 8 | 9 | &:last-child { 10 | @apply pointer-events-none; 11 | } 12 | } 13 | 14 | &__link { 15 | @apply flex text-2xl text-[var(--breadcrumb-text-color)] hover:text-[var(--breadcrumb-text-color-hover)] gap-2 items-center; 16 | 17 | &:last-child { 18 | @apply font-bold !text-[var(--breadcrumb-text-color-active)]; 19 | } 20 | 21 | &:not(:last-child) { 22 | @apply hover:underline font-medium; 23 | } 24 | } 25 | 26 | &__separator { 27 | @apply flex items-center mx-2 text-[var(--breadcrumb-text-color)]; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | pnpm-debug.log* 8 | lerna-debug.log* 9 | 10 | node_modules 11 | .DS_Store 12 | dist 13 | types 14 | dist-ssr 15 | coverage 16 | *.local 17 | storybook-static 18 | 19 | /cypress/videos/ 20 | /cypress/screenshots/ 21 | 22 | # Editor directories and files 23 | .vscode/* 24 | !.vscode/extensions.json 25 | !.vscode/settings.json 26 | .idea 27 | *.suo 28 | *.ntvs* 29 | *.njsproj 30 | *.sln 31 | *.sw? 32 | 33 | # Environment variables 34 | .env 35 | .env.local 36 | .env.development 37 | .env.test 38 | .env.production 39 | 40 | 41 | rollup-docs 42 | 43 | # Yarn 44 | .yarn/* 45 | !.yarn/cache 46 | !.yarn/patches 47 | !.yarn/plugins 48 | !.yarn/releases 49 | !.yarn/sdks 50 | !.yarn/versions -------------------------------------------------------------------------------- /src/components/ProgressBar/RProgressbar.vue: -------------------------------------------------------------------------------- 1 | 24 | 25 | 36 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "prettier.enable": false, 3 | "editor.formatOnSave": false, 4 | "editor.defaultFormatter": "dbaeumer.vscode-eslint", 5 | "editor.codeActionsOnSave": { 6 | "source.fixAll.eslint": "explicit", 7 | "source.organizeImports": "never" 8 | }, 9 | // The following is optional. 10 | // It's better to put under project setting `.vscode/settings.json` 11 | // to avoid conflicts with working with different eslint configs 12 | // that does not support all formats. 13 | "eslint.validate": [ 14 | "javascript", 15 | "javascriptreact", 16 | "typescript", 17 | "typescriptreact", 18 | "vue", 19 | "html", 20 | "markdown", 21 | "json", 22 | "jsonc", 23 | "yaml" 24 | ], 25 | "cSpell.words": [ 26 | "composables", 27 | "jackspeak" 28 | ] 29 | } 30 | -------------------------------------------------------------------------------- /src/components/ProgressBar/Progressbar.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | 3 | import Progressbar from './RProgressbar.vue' 4 | 5 | const ProgressbarStory = { 6 | title: 'Components/Progressbar', 7 | component: Progressbar, 8 | setup: (args: typeof Progressbar) => ({ 9 | args, 10 | }), 11 | template: '', 12 | args: { 13 | value: 50, 14 | height: 8, 15 | }, 16 | argTypes: { 17 | value: { 18 | control: { 19 | type: 'range', 20 | min: 0, 21 | max: 100, 22 | step: 1, 23 | }, 24 | }, 25 | }, 26 | tags: ['autodocs'], 27 | } as Meta 28 | 29 | export default ProgressbarStory 30 | 31 | type Story = StoryObj 32 | 33 | export const Overview: Story = {} 34 | -------------------------------------------------------------------------------- /src/components/TabItem/TabItem.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | 5 | import TabItem from './RTabItem.vue' 6 | 7 | describe('TabItem', () => { 8 | it('renders properly', () => { 9 | const wrapper = mount(TabItem, { 10 | props: { 11 | id: 'tab-1', 12 | label: 'Tab 1', 13 | icon: 'face', 14 | active: true, 15 | }, 16 | }) 17 | expect(wrapper.exists()).toBe(true) 18 | expect(wrapper.find('button').exists()).toBe(true) 19 | expect(wrapper.find('button').html().includes('Tab 1')) 20 | expect( 21 | wrapper 22 | .find('button') 23 | .trigger('click') 24 | .then(() => { 25 | expect(wrapper.emitted('select')).toBeTruthy() 26 | }), 27 | ) 28 | }) 29 | }) 30 | -------------------------------------------------------------------------------- /.github/workflows/chromatic.yml: -------------------------------------------------------------------------------- 1 | name: 'Chromatic' 2 | 3 | on: workflow_dispatch 4 | 5 | jobs: 6 | chromatic-deployment: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - name: Checkout repository 10 | uses: actions/checkout@v3 11 | with: 12 | fetch-depth: 0 13 | - name: Set Node env 14 | uses: actions/setup-node@v3 15 | with: 16 | node-version: 18 17 | cache: 'yarn' 18 | - name: Install dependencies 19 | run: yarn 20 | - name: Publish to Chromatic 21 | uses: chromaui/action@v1 22 | with: 23 | projectToken: ${{ secrets.CHROMATIC_PROJECT_TOKEN }} 24 | exitOnceUploaded: true 25 | exitZeroOnChanges: true 26 | buildScriptName: storybook:build 27 | env: 28 | CHROMATIC_RETRIES: 5 29 | -------------------------------------------------------------------------------- /src/components/Pagination/Pagination.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | 3 | import Pagination from './RPagination.vue' 4 | 5 | const PaginationStory = { 6 | title: 'Components/Pagination', 7 | component: Pagination, 8 | setup: (args: typeof Pagination) => ({ 9 | args, 10 | }), 11 | template: '', 12 | args: { 13 | page: 1, 14 | total: 10, 15 | perPage: 10, 16 | totalItems: 100, 17 | infoText: '1-10 of 100', 18 | }, 19 | argTypes: { 20 | 'onUpdate:page': { action: 'update:page' }, 21 | 'onUpdate:perPage': { action: 'update:perPage' }, 22 | }, 23 | tags: ['autodocs'], 24 | } as Meta 25 | 26 | export default PaginationStory 27 | 28 | type Story = StoryObj 29 | 30 | export const Overview: Story = {} 31 | -------------------------------------------------------------------------------- /src/components/Sidebar/Sidebar.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | 3 | import Sidebar from './RSidebar.vue' 4 | 5 | const SidebarStory = { 6 | title: 'Components/Sidebar', 7 | component: Sidebar, 8 | setup: (args: typeof Sidebar) => ({ 9 | args, 10 | }), 11 | template: ` 12 |
13 | 14 | test 15 | 18 | 19 |
20 | test 21 |
22 |
23 | `, 24 | } as Meta 25 | 26 | export default SidebarStory 27 | 28 | type Story = StoryObj 29 | 30 | export const Collapsed: Story = {} 31 | 32 | export const Expanded: Story = { 33 | args: { 34 | modelValue: true, 35 | }, 36 | } 37 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue", "src/shims.d.ts"], 3 | "compilerOptions": { 4 | "target": "esnext", 5 | "module": "esnext", 6 | "moduleResolution": "node", 7 | "jsx": "preserve", 8 | "types": ["vitest/globals", "vite/client", "node", "jsdom"], 9 | "composite": true, 10 | "lib": ["esnext", "dom"], 11 | "importHelpers": true, 12 | "outDir": "dist", 13 | "strict": true, 14 | "allowJs": true, 15 | "sourceMap": true, 16 | "resolveJsonModule": true, 17 | "esModuleInterop": true, 18 | "skipLibCheck": true, 19 | "experimentalDecorators": true, 20 | "baseUrl": ".", 21 | "paths": { 22 | "@/*": ["./src"] 23 | }, 24 | "declaration": true, 25 | "declarationDir": "dist/types", 26 | "emitDeclarationOnly": true 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /src/components/Icon/icon.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Icon from './RIcon.vue' 5 | 6 | describe('Icon', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(Icon, { 9 | props: { 10 | name: 'face', 11 | size: '24', 12 | color: 'rgb(255, 255, 255)', 13 | kind: 'outlined', 14 | fontWeight: 'bold', 15 | }, 16 | }) 17 | expect(wrapper.exists()).toBe(true) 18 | expect(wrapper.find('span').exists()).toBe(true) 19 | expect(wrapper.find('span').classes().includes('material-icons-outlined')) 20 | expect(wrapper.find('span').text()).toBe('face') 21 | expect(wrapper.find('span').attributes('style')).toContain( 22 | 'font-size: 24px; font-weight: bold; color: rgb(255, 255, 255);', 23 | ) 24 | }) 25 | }) 26 | -------------------------------------------------------------------------------- /src/components/Modal/modal.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Modal from './RModal.vue' 5 | 6 | describe('Modal', () => { 7 | it('should render correctly', () => { 8 | const wrapper = mount(Modal, { 9 | props: { 10 | title: 'Test', 11 | modelValue: true, 12 | }, 13 | }) 14 | expect(wrapper.find('.modal').exists()).toBe(true) 15 | expect( 16 | wrapper.find('.modal').element.getElementsByClassName('title')[0] 17 | .innerHTML, 18 | ).toBe('Test') 19 | expect(wrapper.find('.dialog').exists()).toBe(true) 20 | expect(wrapper.find('.dialog').attributes('aria-modal')).toBe('true') 21 | expect(wrapper.find('.dialog').attributes('role')).toBe('dialog') 22 | expect(wrapper.find('.dialog').attributes('open')).toBe('true') 23 | }) 24 | }) 25 | -------------------------------------------------------------------------------- /src/components/Tabs/Tabs.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | 5 | import Tabs from './RTabs.vue' 6 | 7 | describe('Tabs', () => { 8 | it('renders properly', () => { 9 | const wrapper = mount(Tabs, { 10 | props: { 11 | tabs: [{ id: 0, label: 'Tab 1', icon: 'face' }], 12 | scrollable: true, 13 | }, 14 | }) 15 | expect(wrapper.exists()).toBe(true) 16 | expect(wrapper.find('.tabs-wrapper').exists()).toBe(true) 17 | expect(wrapper.find('.tab-item').element.innerHTML.includes('Tab 1')) 18 | expect(wrapper.find('.tab-item').element.innerHTML.includes('face')) 19 | expect( 20 | wrapper 21 | .find('.tab-item') 22 | .trigger('click') 23 | .then(() => { 24 | expect(wrapper.emitted('select')).toBeTruthy() 25 | }), 26 | ) 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /.storybook/preview.ts: -------------------------------------------------------------------------------- 1 | // import { withSource, transformSource } from './withSource'; 2 | 3 | // export const parameters = { 4 | // actions: { 5 | // argTypesRegex: '^on[A-Z].*', 6 | // }, 7 | // controls: { 8 | // matchers: { 9 | // color: /(background|color)$/i, 10 | // date: /Date$/, 11 | // }, 12 | // }, 13 | // viewMode: 'docs', 14 | // // layout: 'centered', we must do this in MDX files 15 | // docs: { 16 | // transformSource, 17 | // }, 18 | // }; 19 | 20 | // export const decorators = [withSource]; 21 | 22 | import type { Preview } from '@storybook/vue3'; 23 | 24 | const preview: Preview = { 25 | parameters: { 26 | actions: { argTypesRegex: '^on[A-Z].*' }, 27 | controls: { 28 | matchers: { 29 | color: /(background|color)$/i, 30 | date: /Date$/, 31 | }, 32 | }, 33 | }, 34 | }; 35 | 36 | export default preview; 37 | -------------------------------------------------------------------------------- /src/components/Label/Label.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | 3 | import Label from './RLabel.vue' 4 | 5 | const LabelStory = { 6 | title: 'Form/Label', 7 | render: (args: any) => ({ 8 | components: { Label }, 9 | setup() { 10 | return { 11 | args, 12 | } 13 | }, 14 | template: ` 15 |
16 |
19 | `, 20 | }), 21 | args: { 22 | id: '', 23 | for: '', 24 | text: '', 25 | }, 26 | } as Meta 27 | 28 | export default LabelStory 29 | 30 | type Story = StoryObj 31 | 32 | export const Overview: Story = { 33 | args: { 34 | id: 'label', 35 | for: 'name', 36 | text: 'Name:', 37 | }, 38 | } 39 | -------------------------------------------------------------------------------- /.storybook/source-panel/manager.js: -------------------------------------------------------------------------------- 1 | import { addons } from '@storybook/addons' 2 | import { transformSource } from '../withSource.js' 3 | import { createElement as h } from 'react' 4 | import { SyntaxHighlighter } from '@storybook/components' 5 | 6 | addons.register('source-code', (api) => { 7 | addons.addPanel('source-code/panel', { 8 | title: 'Source Code', 9 | render: ({ active, key }) => { 10 | if (!active) return null; 11 | 12 | const story = api.getCurrentStoryData(); 13 | try { 14 | const code = transformSource(story.parameters.storySource.source, story); 15 | return h(SyntaxHighlighter, { 16 | key, 17 | padded: true, 18 | language: 'html', 19 | copyable: true, 20 | }, code); 21 | } catch (e) { 22 | console.warn('Failed to render code', e); 23 | return null; 24 | } 25 | }, 26 | paramKey: 'source', 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/components/Button/Button.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Button from './RButton.vue' 5 | 6 | describe('Button', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(Button, { 9 | props: { 10 | variant: 'default', 11 | size: 'large', 12 | type: 'button', 13 | }, 14 | }) 15 | expect(wrapper.classes()).toContain('r-button') 16 | expect(wrapper.classes()).toContain('r-button__default--primary') 17 | expect(wrapper.attributes('type')).toBe('button') 18 | expect(wrapper.exists()).toBe(true) 19 | expect(wrapper.find('.r-button').attributes('type')).toBe('button') 20 | }) 21 | }) 22 | describe('when clicked', () => { 23 | it('emits an event', () => { 24 | const wrapper = mount(Button) 25 | wrapper.trigger('click') 26 | expect(wrapper.emitted().click).toBeTruthy() 27 | }) 28 | }) 29 | -------------------------------------------------------------------------------- /src/components/Icon/Icon.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | import { action } from '@storybook/addon-actions' 3 | 4 | import Icon from './RIcon.vue' 5 | 6 | const DefaultArgs = {} 7 | 8 | const DefaultArgTypes = {} 9 | 10 | // More on how to set up stories at: https://storybook.js.org/docs/vue/writing-stories/introduction 11 | const IconStory = { 12 | title: 'Components/Icon', 13 | component: Icon, 14 | setup: (args: typeof Icon) => ({ 15 | args, 16 | onClick: action('click'), 17 | }), 18 | template: '', 19 | // This component will have an automatically generated docsPage entry: https://storybook.js.org/docs/vue/writing-docs/autodocs 20 | tags: [], 21 | args: DefaultArgs, 22 | argTypes: DefaultArgTypes, 23 | } as Meta 24 | 25 | export default IconStory 26 | 27 | type Story = StoryObj 28 | 29 | export const Overview: Story = { 30 | args: { 31 | name: 'mdiFaceMan', 32 | }, 33 | } 34 | -------------------------------------------------------------------------------- /src/components/TabItem/tab-item.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-tab-item { 4 | @apply border-b-2 border-transparent transition-all duration-300 gap-2 min-w-fit py-3 px-4 5 | bg-transparent flex justify-center items-center text-[var(--neutral-500)] text-sm 6 | cursor-pointer whitespace-nowrap; 7 | 8 | &:disabled { 9 | @apply cursor-not-allowed bg-inherit opacity-50; 10 | } 11 | 12 | &[aria-selected='true']:not(.r-tab-item--tile) { 13 | @apply text-[var(--primary-500)] border-b-2 border-[var(--primary-500)] font-semibold; 14 | } 15 | 16 | &--text { 17 | @apply px-5; 18 | } 19 | 20 | &--tile { 21 | @apply border-none py-2.5 px-7 text-[var(--neutral-900)] font-normal; 22 | 23 | &[aria-selected='true'] { 24 | @apply bg-white rounded-md shadow-sm text-[var(--neutral-900)]; 25 | } 26 | 27 | &:hover { 28 | @apply text-[var(--neutral-900)] bg-white rounded-md shadow-sm; 29 | } 30 | } 31 | 32 | &--block { 33 | @apply w-full; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /src/components/Tooltip/tooltip.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-tooltip-trigger { 4 | @apply relative inline-block cursor-pointer bg-transparent rounded-full; 5 | } 6 | .r-tooltip { 7 | @apply z-50 bg-transparent m-0 p-0 top-0 left-0 absolute hidden; 8 | 9 | &__content { 10 | @apply font-normal p-4 rounded-lg text-xs; 11 | 12 | &--dark { 13 | @apply text-white bg-[var(--neutral-900)]; 14 | } 15 | &--light { 16 | @apply text-[var(--neutral-900)] bg-white; 17 | } 18 | } 19 | 20 | &__arrow { 21 | @apply absolute w-4 h-4 rotate-45 -z-10 rounded-sm; 22 | 23 | &--dark { 24 | @apply bg-[var(--neutral-900)]; 25 | } 26 | &--light { 27 | @apply bg-white; 28 | } 29 | } 30 | 31 | &--light { 32 | @apply shadow-2xl rounded-lg; 33 | } 34 | 35 | &--dropdown { 36 | @apply z-[10000]; 37 | } 38 | } 39 | 40 | @keyframes r-tooltip-show { 41 | 0% { 42 | opacity: 0; 43 | } 44 | 45 | 100% { 46 | opacity: 1; 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /src/components/Alert/alert.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | import { mount } from '@vue/test-utils' 3 | import Alert from './RAlert.vue' 4 | 5 | describe('Alert', () => { 6 | it('should render correctly', () => { 7 | const wrapper = mount(Alert, { 8 | props: { 9 | type: 'success', 10 | title: 'Alert Title', 11 | description: 'Alert Description', 12 | size: 'small', 13 | closable: true, 14 | }, 15 | }) 16 | 17 | expect(wrapper.classes()).toContain('alert') 18 | expect(wrapper.find('.texts__title').text()).toBe('Alert Title') 19 | expect(wrapper.find('.texts__description').text()).toBe( 20 | 'Alert Description', 21 | ) 22 | expect(wrapper.find('.alert').classes()).toContain('alert--small') 23 | expect( 24 | wrapper 25 | .find('.alert') 26 | .trigger('click') 27 | .then(() => { 28 | expect(wrapper.emitted('close')).toBeTruthy() 29 | }), 30 | ) 31 | }) 32 | }) 33 | -------------------------------------------------------------------------------- /src/components/Switch/switch.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | import Switch from './RSwitch.vue' 5 | 6 | describe('Switch', () => { 7 | it('renders properly', () => { 8 | const wrapper = mount(Switch, { 9 | props: { 10 | id: 'switch-id', 11 | disabled: false, 12 | size: 'small', 13 | }, 14 | }) 15 | expect(wrapper.exists()).toBe(true) 16 | expect(wrapper.find('label').exists()).toBe(true) 17 | expect(wrapper.find('label').element.getAttribute('for')).toBe('switch-id') 18 | expect(wrapper.find('input').exists()).toBe(true) 19 | expect(wrapper.find('input').element.getAttribute('id')).toBe('switch-id') 20 | expect(wrapper.find('input').element.getAttribute('type')).toBe('checkbox') 21 | expect(wrapper.find('input').element.getAttribute('disabled')).toBe(null) 22 | expect( 23 | wrapper 24 | .find('.switch') 25 | .trigger('click') 26 | .then(() => { 27 | expect(wrapper.find('input').element.checked).toBe(true) 28 | }), 29 | ) 30 | }) 31 | }) 32 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Teknasyon 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /src/components/Sidebar/sidebar.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | 5 | import Sidebar from './RSidebar.vue' 6 | 7 | describe('Sidebar', () => { 8 | it('renders correctly', () => { 9 | const wrapper = mount(Sidebar, { 10 | slots: { 11 | default: 'test default', 12 | actions: 'test actions', 13 | }, 14 | }) 15 | expect(wrapper.find('.default').text()).toBe('test default') 16 | expect(wrapper.find('.actions').text()).toBe('test actions') 17 | }) 18 | 19 | it('renders correctly when open', () => { 20 | const wrapper = mount(Sidebar, { 21 | props: { 22 | modelValue: true, 23 | }, 24 | slots: { 25 | default: 'test default', 26 | actions: 'test actions', 27 | }, 28 | }) 29 | expect(wrapper.find('.default').text()).toBe('test default') 30 | expect(wrapper.find('.actions').text()).toBe('test actions') 31 | expect(wrapper.find('.sidebar').classes()).toContain('sidebar--open') 32 | }) 33 | }) 34 | -------------------------------------------------------------------------------- /src/components/Radio/Radio.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | 3 | import Radio from './RRadio.vue' 4 | 5 | const DefaultArgs = { 6 | id: 'radio', 7 | modelValue: false, 8 | name: 'radio-group', 9 | disabled: false, 10 | label: 'Save my login details for next time.', 11 | hint: 'A control used to switch between two states: often on or off.', 12 | errorMsg: '', 13 | title: 'Radio Title', 14 | } 15 | 16 | const RadioStory = { 17 | title: 'Form/Radio', 18 | component: Radio, 19 | setup: (args: typeof Radio) => ({ 20 | args, 21 | }), 22 | template: '', 23 | args: DefaultArgs, 24 | argTypes: { 25 | }, 26 | tags: ['autodocs'], 27 | } as Meta 28 | 29 | export default RadioStory 30 | 31 | type Story = StoryObj 32 | 33 | export const Overview: Story = {} 34 | 35 | export const Checked: Story = { 36 | args: { 37 | modelValue: true, 38 | }, 39 | } 40 | 41 | export const Disabled: Story = { 42 | args: { 43 | disabled: true, 44 | }, 45 | } 46 | 47 | export const Error: Story = { 48 | args: { 49 | errorMsg: 'This is an error message', 50 | }, 51 | } 52 | -------------------------------------------------------------------------------- /src/components/Pagination/pagination.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-pagination { 4 | @apply flex items-center w-full; 5 | 6 | &--left { 7 | @apply justify-start; 8 | } 9 | 10 | &--center { 11 | @apply justify-center; 12 | } 13 | 14 | &--right { 15 | @apply justify-end; 16 | } 17 | 18 | &__paginator { 19 | @apply flex items-center gap-2 whitespace-nowrap; 20 | 21 | &__prev, 22 | &__next, 23 | &__first, 24 | &__last { 25 | @apply flex items-center justify-center bg-[var(--pagination-bg-color)] 26 | cursor-pointer p-[var(--pagination-btn-padding)] rounded-[var(--pagination-border-radius)]; 27 | 28 | &:hover:not(:disabled) { 29 | @apply !text-[var(--pagination-hover-color)] bg-[var(--pagination-hover-bg)] transition-all; 30 | 31 | svg { 32 | @apply stroke-[var(--pagination-hover-color)]; 33 | } 34 | } 35 | 36 | &:disabled { 37 | @apply cursor-not-allowed; 38 | } 39 | } 40 | 41 | &__per-page { 42 | @apply flex items-center gap-8 text-sm; 43 | } 44 | 45 | &__info { 46 | @apply flex items-center justify-center text-sm min-w-[122px]; 47 | } 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { resolve } from 'node:path' 2 | import { defineConfig } from 'vite' 3 | import vue from '@vitejs/plugin-vue' 4 | import dts from 'vite-plugin-dts' 5 | 6 | // https://vitejs.dev/config/ 7 | export default defineConfig({ 8 | plugins: [ 9 | dts({ 10 | copyDtsFiles: true, 11 | outDir: [ 12 | 'dist', 13 | 'types', 14 | // 'types/inner' 15 | ], 16 | // include: ['src/index.ts'], 17 | exclude: ['src/ignore'], 18 | staticImport: true, 19 | rollupTypes: false, 20 | // insertTypesEntry: true, 21 | compilerOptions: { 22 | declarationMap: true, 23 | }, 24 | rollupConfig: { 25 | docModel: { 26 | enabled: true, 27 | apiJsonFilePath: '/rollup-docs/.api.json', 28 | }, 29 | }, 30 | }), 31 | vue(), 32 | ], 33 | resolve: { 34 | alias: { 35 | '@': resolve(__dirname, 'src/'), 36 | }, 37 | }, 38 | build: { 39 | lib: { 40 | entry: resolve(__dirname, 'src/lib/main.ts'), 41 | name: 'rocket-ui-vue', 42 | fileName: 'rocket-ui-vue', 43 | }, 44 | rollupOptions: { 45 | external: ['vue'], 46 | }, 47 | }, 48 | }) 49 | -------------------------------------------------------------------------------- /src/components/Modal/modal.css: -------------------------------------------------------------------------------- 1 | @import '../../index.css'; 2 | 3 | .r-modal-overlay { 4 | @apply fixed inset-0 z-50 flex items-center justify-center 5 | bg-black bg-opacity-50 transition-opacity duration-300 ease-in-out; 6 | } 7 | 8 | .r-dialog { 9 | @apply w-[var(--modal-width)] rounded-[var(--modal-border-radius)] 10 | shadow-[var(--modal-shadow)] p-[var(--modal-padding)] 11 | flex flex-col gap-6 bg-white animate-[var(--modal-animation)] transition-[width] duration-300 ease-in; 12 | 13 | &__header { 14 | @apply flex flex-col items-start justify-between gap-4; 15 | 16 | .icon { 17 | @apply max-h-[var(--modal-icon-size)] max-w-[var(--modal-icon-size)] p-[var(--modal-icon-padding)] 18 | grid items-center bg-[var(--modal-icon-bg)] rounded-full text-[var(--modal-icon-color)]; 19 | } 20 | 21 | .title { 22 | @apply text-2xl font-semibold; 23 | } 24 | 25 | .description { 26 | @apply text-sm text-[var(--neutral-500)] font-normal; 27 | } 28 | } 29 | 30 | &__body { 31 | @apply flex-1 overflow-y-auto; 32 | } 33 | 34 | &__actions { 35 | @apply flex items-center justify-end gap-[var(--space-4)]; 36 | } 37 | 38 | &--block { 39 | @apply w-full; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /.github/workflows/publish-storybook.yml: -------------------------------------------------------------------------------- 1 | # eslint-disable 2 | name: Publish Storybook 3 | 4 | on: 5 | push: 6 | tags: ['v*'] 7 | 8 | workflow_dispatch: 9 | 10 | 11 | permissions: 12 | contents: read 13 | pages: write 14 | id-token: write 15 | 16 | concurrency: 17 | group: 'pages' 18 | cancel-in-progress: true 19 | 20 | jobs: 21 | # Single deploy job since we're just deploying 22 | deploy: 23 | environment: 24 | name: github-pages 25 | url: ${{ steps.deployment.outputs.page_url }} 26 | runs-on: ubuntu-latest 27 | steps: 28 | - name: Checkout 29 | uses: actions/checkout@v3 30 | - name: Set Node env 31 | uses: actions/setup-node@v3 32 | with: 33 | node-version: 20 34 | cache: 'yarn' 35 | - name: Install dependencies 36 | run: yarn install --immutable 37 | - name: Build Storybook 38 | run: yarn storybook:build 39 | - name: Setup Pages 40 | uses: actions/configure-pages@v3 41 | - name: Upload artifact 42 | uses: actions/upload-pages-artifact@v1 43 | with: 44 | path: './storybook-static/.' 45 | - name: Deploy to GitHub Pages 46 | id: deployment 47 | uses: actions/deploy-pages@v2 48 | -------------------------------------------------------------------------------- /src/components/Chips/chip.spec.ts: -------------------------------------------------------------------------------- 1 | import { describe, expect, it } from 'vitest' 2 | 3 | import { mount } from '@vue/test-utils' 4 | 5 | import Chip from './RChip.vue' 6 | 7 | describe('Chip', () => { 8 | it('renders properly', () => { 9 | const wrapper = mount(Chip, { 10 | props: { 11 | variant: 'primary', 12 | label: 'Hello', 13 | size: 'small', 14 | appendIcon: 'close', 15 | }, 16 | }) 17 | expect(wrapper.exists()).toBe(true) 18 | expect(wrapper.classes()).toContain('chip') 19 | expect(wrapper.classes()).toContain('chip__primary') 20 | expect(wrapper.classes()).toContain('chip--small') 21 | // expect(wrapper.find('.chip__text').text()).toBe('Hello'); 22 | // expect( 23 | // wrapper 24 | // .find('.chip__text') 25 | // .trigger('click') 26 | // .then(() => { 27 | // expect(wrapper.emitted('clickChip')).toBeTruthy(); 28 | // }) 29 | // ); 30 | expect(wrapper.find('.chip__append-icon').exists()).toBe(true) 31 | expect( 32 | wrapper 33 | .find('.chip__append-icon') 34 | .trigger('click') 35 | .then(() => { 36 | expect(wrapper.emitted('clickIcon')).toBeTruthy() 37 | }), 38 | ) 39 | }) 40 | }) 41 | -------------------------------------------------------------------------------- /src/components/TextArea/TextArea.stories.ts: -------------------------------------------------------------------------------- 1 | import type { Meta, StoryObj } from '@storybook/vue3' 2 | import TextArea from './RTextArea.vue' 3 | 4 | const DefaultArgs = { 5 | errorMsg: '', 6 | label: 'Text Area', 7 | placeholder: 'RocketUI', 8 | modelValue: '', 9 | disabled: false, 10 | hint: 'A form control for editing multi-line text.', 11 | } 12 | 13 | const TextAreaStory = { 14 | title: 'Form/TextArea', 15 | component: TextArea, 16 | setup(args: typeof TextArea) { 17 | return { args } 18 | }, 19 | template: '