Initial commit

This commit is contained in:
Benjamin Canac
2021-11-16 12:49:00 +01:00
commit d1902448ae
19 changed files with 7375 additions and 0 deletions

13
.editorconfig Executable file
View File

@@ -0,0 +1,13 @@
# editorconfig.org
root = true
[*]
indent_size = 2
indent_style = space
end_of_line = lf
charset = utf-8
trim_trailing_whitespace = true
insert_final_newline = true
[*.md]
trim_trailing_whitespace = false

14
.eslintignore Normal file
View File

@@ -0,0 +1,14 @@
node_modules
dist
.nuxt
coverage
*.log*
.DS_Store
.code
*.iml
package-lock.json
templates/*
sw.js
# Templates
src/templates

5
.eslintrc Normal file
View File

@@ -0,0 +1,5 @@
{
"extends": [
"@nuxtjs/eslint-config-typescript"
]
}

60
.github/workflows/ci-dev.yml vendored Normal file
View File

@@ -0,0 +1,60 @@
name: ci-dev
on:
push:
branches:
- dev
pull_request:
branches:
- dev
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [16]
steps:
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Checkout
uses: actions/checkout@master
with:
persist-credentials: false
fetch-depth: 0
- name: Cache
uses: actions/cache@v2
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
- name: Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn
- name: Lint
run: yarn lint
- name: Prepare
run: node ./node_modules/playwright/install.js
- name: Test
run: yarn test
- name: Coverage
uses: codecov/codecov-action@v2
- name: Build
run: yarn build
- name: Release Edge
if: github.event_name == 'push'
run: ./scripts/release-edge.sh
env:
NODE_AUTH_TOKEN: ${{secrets.NODE_AUTH_TOKEN}}

66
.github/workflows/ci.yml vendored Normal file
View File

@@ -0,0 +1,66 @@
name: ci-main
on:
push:
branches:
- main
pull_request:
branches:
- main
jobs:
ci:
runs-on: ${{ matrix.os }}
strategy:
matrix:
os: [ubuntu-latest] # macos-latest, windows-latest
node: [16]
steps:
- uses: actions/setup-node@v2
with:
node-version: ${{ matrix.node }}
- name: Checkout
uses: actions/checkout@master
with:
persist-credentials: false
fetch-depth: 0
- name: Cache
uses: actions/cache@v2
with:
path: node_modules
key: ${{ matrix.os }}-node-v${{ matrix.node }}-deps-${{ hashFiles(format('{0}{1}', github.workspace, '/yarn.lock')) }}
- name: Dependencies
if: steps.cache.outputs.cache-hit != 'true'
run: yarn
- name: Lint
run: yarn lint
- name: Prepare
run: node ./node_modules/playwright/install.js
- name: Test
run: yarn test
- name: Coverage
uses: codecov/codecov-action@v2
- name: Build
run: yarn build
- name: Version Check
id: check
uses: EndBug/version-check@v2
with:
token: ${{ secrets.GITHUB_TOKEN }}
- name: Release
if: github.event_name == 'push' && steps.check.outputs.changed == 'true'
run: ./scripts/release.sh
env:
NODE_AUTH_TOKEN: ${{ secrets.NODE_AUTH_TOKEN }}

6
.gitignore vendored Normal file
View File

@@ -0,0 +1,6 @@
node_modules
*.log
.nuxt
nuxt.d.ts
.output
dist

42
README.md Normal file
View File

@@ -0,0 +1,42 @@
# @nuxthq/ui
Components library as a Nuxt3 module.
## Installation
```bash
yarn add --dev @nuxthq/ui
```
Register the module in your `nuxt.config.js`:
```js
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
buildModules: [
'@nuxthq/ui'
]
})
```
## Options
- `primary`
Define the primary variant. Defaults to `indigo`.
**Example:**
```js
import { defineNuxtConfig } from 'nuxt3'
export default defineNuxtConfig({
buildModules: [
'@nuxthq/ui'
],
ui: {
primary: 'blue'
}
})
```

9
build.config.ts Normal file
View File

@@ -0,0 +1,9 @@
export default {
entries: [
'./src/index'
],
declaration: true,
externals: [
'@nuxt/kit'
]
}

8
example/app.vue Normal file
View File

@@ -0,0 +1,8 @@
<template>
<div>
Welcome
<NuButton class="ml-3" variant="primary" icon="heroicons-outline:check-circle">
toto
</NuButton>
</div>
</template>

8
example/nuxt.config.ts Normal file
View File

@@ -0,0 +1,8 @@
import { defineNuxtConfig } from 'nuxt3'
// https://v3.nuxtjs.org/docs/directory-structure/nuxt.config
export default defineNuxtConfig({
buildModules: [
['../src', { primary: 'blue', prefix: 'nu' }]
]
})

38
package.json Normal file
View File

@@ -0,0 +1,38 @@
{
"name": "@nuxthq/ui",
"version": "0.0.1",
"repository": "https://github.com/nuxtlabs/ui",
"license": "MIT",
"exports": {
".": {
"require": "./dist/index.cjs",
"import": "./dist/index.mjs"
}
},
"main": "dist/index.cjs",
"module": "dist/index.mjs",
"types": "dist/index.d.ts",
"files": [
"dist"
],
"scripts": {
"build": "unbuild",
"dev": "nuxi dev example",
"lint": "eslint --ext .ts,.js,.vue ."
},
"dependencies": {
"@unocss/core": "^0.9.3",
"@unocss/nuxt": "^0.9.3",
"@unocss/preset-icons": "^0.9.3",
"@unocss/preset-uno": "^0.9.3",
"@unocss/reset": "^0.9.3",
"pathe": "^0.2.0"
},
"devDependencies": {
"@iconify-json/heroicons-outline": "^1.0.2",
"@nuxtjs/eslint-config-typescript": "^7.0.2",
"eslint": "^8.2.0",
"nuxt3": "latest",
"unbuild": "^0.5.11"
}
}

37
scripts/bump-edge.ts Normal file
View File

@@ -0,0 +1,37 @@
import { promises as fsp } from 'fs'
import { resolve } from 'path'
import { execSync } from 'child_process'
async function loadPackage (dir: string) {
const pkgPath = resolve(dir, 'package.json')
const data = JSON.parse(await fsp.readFile(pkgPath, 'utf-8').catch(() => '{}'))
const save = () => fsp.writeFile(pkgPath, JSON.stringify(data, null, 2) + '\n')
return {
dir,
data,
save
}
}
async function main () {
const pkg = await loadPackage(process.cwd())
const commit = execSync('git rev-parse --short HEAD').toString('utf-8').trim()
const date = Math.round(Date.now() / (1000 * 60))
pkg.data.name = `${pkg.data.name}-edge`
pkg.data.version = `${pkg.data.version}-${date}.${commit}`
pkg.save()
}
main().catch((err) => {
// eslint-disable-next-line no-console
console.error(err)
process.exit(1)
})

22
scripts/release-edge.sh Executable file
View File

@@ -0,0 +1,22 @@
#!/bin/bash
# Restore all git changes
git restore -s@ -SW -- example src test
# Bump versions to edge
yarn jiti ./scripts/bump-edge
# Resolve yarn
yarn
# Update token
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
echo "always-auth=true" >> ~/.npmrc
npm whoami
fi
# Release package
echo "Publishing @nuxthq/ui"
npm publish -q --access public

19
scripts/release.sh Executable file
View File

@@ -0,0 +1,19 @@
#!/bin/bash
# Restore all git changes
git restore -s@ -SW -- example src test
# Resolve yarn
yarn
# Update token
if [[ ! -z ${NODE_AUTH_TOKEN} ]] ; then
echo "//registry.npmjs.org/:_authToken=${NODE_AUTH_TOKEN}" >> ~/.npmrc
echo "registry=https://registry.npmjs.org/" >> ~/.npmrc
echo "always-auth=true" >> ~/.npmrc
npm whoami
fi
# Release package
echo "Publishing @nuxthq/ui"
npm publish -q --access public

319
src/components/Button.vue Normal file
View File

@@ -0,0 +1,319 @@
<template>
<component
:is="is"
:class="buttonClass"
:aria-label="ariaLabel"
v-bind="props"
>
<Icon v-if="isLeading" :name="iconName" :class="leadingIconClass" aria-hidden="true" />
<slot><span :class="truncate ? 'text-left break-all line-clamp-1' : ''">{{ label }}</span></slot>
<Icon v-if="isTrailing" :name="iconName" :class="trailingIconClass" aria-hidden="true" />
</component>
</template>
<script>
import Icon from './Icon'
export default {
components: {
Icon
},
props: {
type: {
type: String,
default: 'button'
},
block: {
type: Boolean,
default: false
},
label: {
type: String,
default: null
},
loading: {
type: Boolean,
default: false
},
disabled: {
type: Boolean,
default: false
},
size: {
type: String,
default: 'md',
validator (value) {
return ['', 'xxs', 'xs', 'sm', 'md', 'lg', 'xl'].includes(value)
}
},
variant: {
type: String,
default: 'primary',
validator (value) {
return ['primary', 'secondary', 'danger', 'white', 'gray', 'gray-hover', 'white-hover', 'black', 'black-hover', 'transparent', 'link', 'gradient', 'custom'].includes(value)
}
},
icon: {
type: String,
default: null
},
loadingIcon: {
type: String,
default: null
},
trailing: {
type: Boolean,
default: false
},
leading: {
type: Boolean,
default: false
},
href: {
type: String,
default: null
},
to: {
type: [String, Object],
default: null
},
target: {
type: String,
default: null
},
ariaLabel: {
type: String,
default: null
},
rounded: {
type: Boolean,
default: false
},
iconClass: {
type: String,
default: ''
},
baseClass: {
type: String,
default: 'font-medium focus:outline-none disabled:cursor-not-allowed disabled:opacity-75'
},
customClass: {
type: String,
default: null
},
square: {
type: Boolean,
default: false
},
truncate: {
type: Boolean,
default: false
},
noFocusBorder: {
type: Boolean,
default: false
},
noPadding: {
type: Boolean,
default: false
}
},
computed: {
is () {
if (this.href) {
return 'a'
} else if (this.to) {
return 'NuxtLink'
}
return 'button'
},
props () {
switch (this.is) {
case 'a':
return {
href: this.href,
target: this.target
}
case 'NuxtLink': {
return {
to: this.to
}
}
default: {
return {
disabled: this.disabled || this.loading,
type: this.type
}
}
}
},
isLeading () {
return (this.leading && this.icon) || (this.icon && !this.trailing) || (this.loading && !this.trailing)
},
isTrailing () {
return (this.trailing && this.icon) || (this.loading && this.trailing)
},
sizeClass () {
return ({
xxs: 'text-xs',
xs: 'text-xs',
sm: 'text-sm leading-4',
md: 'text-sm',
lg: 'text-base',
xl: 'text-base'
})[this.size]
},
paddingClass () {
if (this.noPadding) {
return ''
}
const isSquare = this.square || (!this.$slots.default && !this.label)
return ({
true: {
xxs: 'px-2 py-1',
xs: 'px-2.5 py-1.5',
sm: 'px-3 py-2',
md: 'px-4 py-2',
lg: 'px-4 py-2',
xl: 'px-6 py-3'
},
false: {
xxs: 'p-1',
xs: 'p-1.5',
sm: 'p-2',
md: 'p-2',
lg: 'p-2',
xl: 'p-3'
}
})[!isSquare][this.size]
},
variantClass () {
return ({
primary: 'shadow-sm border border-transparent text-white bg-primary-600 hover:bg-primary-700 disabled:bg-primary-600',
secondary: 'border border-transparent text-primary-700 bg-primary-100 hover:bg-primary-200 disabled:bg-primary-100',
danger: 'shadow-sm border border-transparent text-white bg-red-500 dark:bg-red-600 hover:bg-red-600 dark:hover:bg-red-500 disabled:bg-red-500 dark:disabled:bg-red-600',
white: 'shadow-sm border border-tw-gray-300 text-tw-gray-700 bg-tw-white hover:bg-tw-gray-50 disabled:bg-tw-white',
'white-hover': 'border border-transparent text-tw-gray-500 hover:text-tw-gray-700 focus:text-tw-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-900 dark:focus:bg-gray-900 disabled:text-tw-gray-500',
gray: 'shadow-sm border border-tw-gray-300 text-tw-gray-500 hover:text-tw-gray-700 focus:text-tw-gray-700 bg-gray-50 dark:bg-gray-800 disabled:text-tw-gray-500',
'gray-hover': 'border border-transparent text-tw-gray-500 hover:text-tw-gray-700 focus:text-tw-gray-700 bg-transparent hover:bg-gray-100 focus:bg-gray-100 dark:hover:bg-gray-800 dark:focus:bg-gray-800 disabled:text-tw-gray-500',
black: 'border border-transparent text-tw-white bg-tw-gray-800 hover:bg-tw-gray-900 focus:bg-tw-gray-900',
'black-hover': 'border border-transparent text-tw-gray-500 hover:text-tw-gray-900 focus:text-tw-gray-700 bg-transparent hover:bg-white dark:hover:bg-black focus:bg-white dark:focus:bg-black',
transparent: 'border border-transparent text-tw-gray-500 hover:text-tw-gray-700 focus:text-tw-gray-700 disabled:hover:text-tw-gray-500',
link: 'border border-transparent text-primary-500 hover:text-primary-700 focus:text-primary-700',
gradient: 'shadow-sm text-white border border-transparent bg-gradient-to-r from-indigo-600 to-blue-600 hover:from-indigo-700 hover:to-blue-700',
custom: ''
})[this.variant]
},
variantFocusBorderClass () {
if (this.noFocusBorder) {
return ''
}
return ({
primary: 'focus:ring-2 focus:ring-primary-200',
secondary: 'focus:ring-2 focus:ring-primary-500',
white: 'focus:ring-1 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'white-hover': '',
gray: 'focus:ring-1 focus:ring-primary-500 focus:border-primary-500 dark:focus:border-primary-500',
'gray-hover': '',
link: '',
transparent: '',
custom: ''
})[this.variant]
},
blockClass () {
return ({
true: 'w-full flex justify-center items-center',
false: 'inline-flex items-center'
})[this.block]
},
roundedClass () {
return ({
true: 'rounded-full',
false: 'rounded-md'
})[this.rounded]
},
iconName () {
if (this.loading) {
return this.loadingIcon || 'custom/loading'
}
return this.icon
},
loadingIconClass () {
return [
({
true: 'animate-spin'
})[this.loading]
]
},
leadingIconClass () {
return [
this.iconClass,
'flex-shrink-0',
...this.loadingIconClass,
({
xxs: 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-5 w-5'
})[this.size || 'sm'],
({
true: {
xxs: '-ml-0.5 mr-1',
xs: '-ml-0.5 mr-1.5',
sm: '-ml-0.5 mr-2',
md: '-ml-1 mr-2',
lg: '-ml-1 mr-3',
xl: '-ml-1 mr-3'
},
false: {}
})[!!this.$slots.default || !!(this.label?.length)][this.size]
].join(' ')
},
trailingIconClass () {
return [
this.iconClass,
'flex-shrink-0',
...this.loadingIconClass,
({
xxs: 'h-3.5 w-3.5',
xs: 'h-4 w-4',
sm: 'h-4 w-4',
md: 'h-5 w-5',
lg: 'h-5 w-5',
xl: 'h-5 w-5'
})[this.size || 'sm'],
({
true: {
xxs: 'ml-1 -mr-0.5',
xs: 'ml-1.5 -mr-0.5',
sm: 'ml-2 -mr-0.5',
md: 'ml-2 -mr-1',
lg: 'ml-3 -mr-1',
xl: 'ml-3 -mr-1'
},
false: {}
})[!!this.$slots.default || !!(this.label?.length)][this.size]
].join(' ')
},
buttonClass () {
return [
this.baseClass,
this.roundedClass,
this.sizeClass,
this.paddingClass,
this.variantClass,
this.variantFocusBorderClass,
this.blockClass,
this.customClass
].filter(Boolean).join(' ')
}
}
}
</script>

14
src/components/Icon.vue Normal file
View File

@@ -0,0 +1,14 @@
<template>
<i :class="name" />
</template>
<script>
export default {
props: {
name: {
type: String,
required: true
}
}
}
</script>

34
src/index.ts Normal file
View File

@@ -0,0 +1,34 @@
import { join } from 'pathe'
import { defineNuxtModule, installModule } from '@nuxt/kit'
import presetUno, { colors } from '@unocss/preset-uno'
import presetIcons from '@unocss/preset-icons'
export default defineNuxtModule({
async setup (_options, nuxt) {
const options = {
theme: {
colors: {
primary: colors ? colors[_options?.primary || 'indigo'] : undefined
}
},
presets: [
presetUno(),
presetIcons({
prefix: ''
})
]
}
await installModule(nuxt, { src: '@unocss/nuxt', options })
nuxt.hook('components:dirs', (dirs) => {
// Add ./components dir to the list
dirs.push({
path: join(__dirname, 'components'),
prefix: _options.prefix || 'u'
})
})
nuxt.options.css.unshift('@unocss/reset/tailwind.css')
}
})

20
tsconfig.json Normal file
View File

@@ -0,0 +1,20 @@
{
"compilerOptions": {
"baseUrl": ".",
"strict": true,
"target": "ESNext",
"module": "ESNext",
"lib": ["ESNext", "DOM"],
"esModuleInterop": true,
"moduleResolution": "node",
"declaration": false,
"skipLibCheck": true,
"resolveJsonModule": true,
"allowSyntheticDefaultImports": true,
"types": [
"node",
"@nuxt/kit"
],
"allowJs": true
}
}

6641
yarn.lock Normal file

File diff suppressed because it is too large Load Diff