commit
bdd63cda3b
105
.github/workflows/jan-electron-linter-and-test.yml
vendored
105
.github/workflows/jan-electron-linter-and-test.yml
vendored
@ -6,17 +6,17 @@ on:
|
|||||||
- main
|
- main
|
||||||
- dev
|
- dev
|
||||||
paths:
|
paths:
|
||||||
- "electron/**"
|
- 'electron/**'
|
||||||
- .github/workflows/jan-electron-linter-and-test.yml
|
- .github/workflows/jan-electron-linter-and-test.yml
|
||||||
- "web/**"
|
- 'web/**'
|
||||||
- "uikit/**"
|
- 'joi/**'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "node_modules/**"
|
- 'node_modules/**'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
- "core/**"
|
- 'core/**'
|
||||||
- "extensions/**"
|
- 'extensions/**'
|
||||||
- "!README.md"
|
- '!README.md'
|
||||||
- "Makefile"
|
- 'Makefile'
|
||||||
|
|
||||||
pull_request:
|
pull_request:
|
||||||
branches:
|
branches:
|
||||||
@ -24,17 +24,17 @@ on:
|
|||||||
- dev
|
- dev
|
||||||
- release/**
|
- release/**
|
||||||
paths:
|
paths:
|
||||||
- "electron/**"
|
- 'electron/**'
|
||||||
- .github/workflows/jan-electron-linter-and-test.yml
|
- .github/workflows/jan-electron-linter-and-test.yml
|
||||||
- "web/**"
|
- 'web/**'
|
||||||
- "uikit/**"
|
- 'joi/**'
|
||||||
- "package.json"
|
- 'package.json'
|
||||||
- "node_modules/**"
|
- 'node_modules/**'
|
||||||
- "yarn.lock"
|
- 'yarn.lock'
|
||||||
- "Makefile"
|
- 'Makefile'
|
||||||
- "extensions/**"
|
- 'extensions/**'
|
||||||
- "core/**"
|
- 'core/**'
|
||||||
- "!README.md"
|
- '!README.md'
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test-on-macos:
|
test-on-macos:
|
||||||
@ -51,23 +51,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
rm -rf ~/jan
|
rm -rf ~/jan
|
||||||
make clean
|
make clean
|
||||||
|
|
||||||
- name: Get Commit Message for PR
|
- name: Get Commit Message for PR
|
||||||
if : github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}})" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get Commit Message for push event
|
- name: Get Commit Message for push event
|
||||||
if : github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}})" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: "Config report portal"
|
- name: 'Config report portal'
|
||||||
run: |
|
run: |
|
||||||
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App macos" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
|
|
||||||
@ -77,10 +77,10 @@ jobs:
|
|||||||
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||||
TURBO_API: "${{ secrets.TURBO_API }}"
|
TURBO_API: '${{ secrets.TURBO_API }}'
|
||||||
TURBO_TEAM: "macos"
|
TURBO_TEAM: 'macos'
|
||||||
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}'
|
||||||
|
|
||||||
test-on-macos-pr-target:
|
test-on-macos-pr-target:
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
@ -96,7 +96,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
rm -rf ~/jan
|
rm -rf ~/jan
|
||||||
@ -108,14 +108,14 @@ jobs:
|
|||||||
yarn config set registry https://registry.npmjs.org --global
|
yarn config set registry https://registry.npmjs.org --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
CSC_IDENTITY_AUTO_DISCOVERY: "false"
|
CSC_IDENTITY_AUTO_DISCOVERY: 'false'
|
||||||
|
|
||||||
test-on-windows:
|
test-on-windows:
|
||||||
if: github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
strategy:
|
strategy:
|
||||||
fail-fast: false
|
fail-fast: false
|
||||||
matrix:
|
matrix:
|
||||||
antivirus-tools: ['mcafee', 'default-windows-security','bit-defender']
|
antivirus-tools: ['mcafee', 'default-windows-security', 'bit-defender']
|
||||||
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
runs-on: windows-desktop-${{ matrix.antivirus-tools }}
|
||||||
steps:
|
steps:
|
||||||
- name: Getting the repo
|
- name: Getting the repo
|
||||||
@ -129,7 +129,7 @@ jobs:
|
|||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
# Clean cache, continue on error
|
# Clean cache, continue on error
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
@ -142,12 +142,12 @@ jobs:
|
|||||||
make clean
|
make clean
|
||||||
|
|
||||||
- name: Get Commit Message for push event
|
- name: Get Commit Message for push event
|
||||||
if : github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: "Config report portal"
|
- name: 'Config report portal'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows ${{ matrix.antivirus-tools }}" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
@ -159,9 +159,9 @@ jobs:
|
|||||||
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
TURBO_API: "${{ secrets.TURBO_API }}"
|
TURBO_API: '${{ secrets.TURBO_API }}'
|
||||||
TURBO_TEAM: "windows"
|
TURBO_TEAM: 'windows'
|
||||||
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}'
|
||||||
test-on-windows-pr:
|
test-on-windows-pr:
|
||||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository)
|
||||||
runs-on: windows-desktop-default-windows-security
|
runs-on: windows-desktop-default-windows-security
|
||||||
@ -177,7 +177,7 @@ jobs:
|
|||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
# Clean cache, continue on error
|
# Clean cache, continue on error
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
@ -190,12 +190,12 @@ jobs:
|
|||||||
make clean
|
make clean
|
||||||
|
|
||||||
- name: Get Commit Message for PR
|
- name: Get Commit Message for PR
|
||||||
if : github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: "Config report portal"
|
- name: 'Config report portal'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Windows" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
@ -207,9 +207,9 @@ jobs:
|
|||||||
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
TURBO_API: "${{ secrets.TURBO_API }}"
|
TURBO_API: '${{ secrets.TURBO_API }}'
|
||||||
TURBO_TEAM: "windows"
|
TURBO_TEAM: 'windows'
|
||||||
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}'
|
||||||
|
|
||||||
test-on-windows-pr-target:
|
test-on-windows-pr-target:
|
||||||
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
if: github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name != github.repository
|
||||||
@ -226,7 +226,7 @@ jobs:
|
|||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
# Clean cache, continue on error
|
# Clean cache, continue on error
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
shell: powershell
|
shell: powershell
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
@ -245,7 +245,6 @@ jobs:
|
|||||||
yarn config set registry https://registry.npmjs.org --global
|
yarn config set registry https://registry.npmjs.org --global
|
||||||
make test
|
make test
|
||||||
|
|
||||||
|
|
||||||
test-on-ubuntu:
|
test-on-ubuntu:
|
||||||
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
||||||
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
if: (github.event_name == 'pull_request' && github.event.pull_request.head.repo.full_name == github.repository) || github.event_name == 'push' || github.event_name == 'workflow_dispatch'
|
||||||
@ -260,23 +259,23 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
rm -rf ~/jan
|
rm -rf ~/jan
|
||||||
make clean
|
make clean
|
||||||
|
|
||||||
- name: Get Commit Message for PR
|
- name: Get Commit Message for PR
|
||||||
if : github.event_name == 'pull_request'
|
if: github.event_name == 'pull_request'
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.event.after}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: Get Commit Message for push event
|
- name: Get Commit Message for push event
|
||||||
if : github.event_name == 'push'
|
if: github.event_name == 'push'
|
||||||
run: |
|
run: |
|
||||||
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
echo "REPORT_PORTAL_DESCRIPTION=${{github.sha}}" >> $GITHUB_ENV
|
||||||
|
|
||||||
- name: "Config report portal"
|
- name: 'Config report portal'
|
||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
make update-playwright-config REPORT_PORTAL_URL=${{ secrets.REPORT_PORTAL_URL }} REPORT_PORTAL_API_KEY=${{ secrets.REPORT_PORTAL_API_KEY }} REPORT_PORTAL_PROJECT_NAME=${{ secrets.REPORT_PORTAL_PROJECT_NAME }} REPORT_PORTAL_LAUNCH_NAME="Jan App Linux" REPORT_PORTAL_DESCRIPTION="${{env.REPORT_PORTAL_DESCRIPTION}}"
|
||||||
@ -289,9 +288,9 @@ jobs:
|
|||||||
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
yarn config set registry ${{ secrets.NPM_PROXY }} --global
|
||||||
make test
|
make test
|
||||||
env:
|
env:
|
||||||
TURBO_API: "${{ secrets.TURBO_API }}"
|
TURBO_API: '${{ secrets.TURBO_API }}'
|
||||||
TURBO_TEAM: "linux"
|
TURBO_TEAM: 'linux'
|
||||||
TURBO_TOKEN: "${{ secrets.TURBO_TOKEN }}"
|
TURBO_TOKEN: '${{ secrets.TURBO_TOKEN }}'
|
||||||
|
|
||||||
test-on-ubuntu-pr-target:
|
test-on-ubuntu-pr-target:
|
||||||
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
runs-on: [self-hosted, Linux, ubuntu-desktop]
|
||||||
@ -307,7 +306,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 20
|
node-version: 20
|
||||||
|
|
||||||
- name: "Cleanup cache"
|
- name: 'Cleanup cache'
|
||||||
continue-on-error: true
|
continue-on-error: true
|
||||||
run: |
|
run: |
|
||||||
rm -rf ~/jan
|
rm -rf ~/jan
|
||||||
|
|||||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -16,6 +16,7 @@ electron/renderer
|
|||||||
electron/models
|
electron/models
|
||||||
electron/docs
|
electron/docs
|
||||||
electron/engines
|
electron/engines
|
||||||
|
electron/themes
|
||||||
electron/playwright-report
|
electron/playwright-report
|
||||||
server/pre-install
|
server/pre-install
|
||||||
package-lock.json
|
package-lock.json
|
||||||
|
|||||||
@ -39,10 +39,10 @@ COPY --from=builder /app/docs/openapi ./docs/openapi/
|
|||||||
COPY --from=builder /app/pre-install ./pre-install/
|
COPY --from=builder /app/pre-install ./pre-install/
|
||||||
|
|
||||||
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/uikit ./uikit/
|
COPY --from=builder /app/joi ./joi/
|
||||||
COPY --from=builder /app/web ./web/
|
COPY --from=builder /app/web ./web/
|
||||||
|
|
||||||
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
RUN yarn workspace @janhq/joi install && yarn workspace @janhq/joi build
|
||||||
RUN yarn workspace @janhq/web install
|
RUN yarn workspace @janhq/web install
|
||||||
|
|
||||||
RUN npm install -g serve@latest
|
RUN npm install -g serve@latest
|
||||||
|
|||||||
@ -63,10 +63,10 @@ COPY --from=builder /app/docs/openapi ./docs/openapi/
|
|||||||
COPY --from=builder /app/pre-install ./pre-install/
|
COPY --from=builder /app/pre-install ./pre-install/
|
||||||
|
|
||||||
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
# Copy the package.json, yarn.lock, and output of web yarn space to leverage Docker cache
|
||||||
COPY --from=builder /app/uikit ./uikit/
|
COPY --from=builder /app/joi ./joi/
|
||||||
COPY --from=builder /app/web ./web/
|
COPY --from=builder /app/web ./web/
|
||||||
|
|
||||||
RUN yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build
|
RUN yarn workspace @janhq/joi install && yarn workspace @janhq/joi build
|
||||||
RUN yarn workspace @janhq/web install
|
RUN yarn workspace @janhq/web install
|
||||||
|
|
||||||
RUN npm install -g serve@latest
|
RUN npm install -g serve@latest
|
||||||
|
|||||||
8
Makefile
8
Makefile
@ -11,15 +11,15 @@ all:
|
|||||||
@echo "Specify a target to run"
|
@echo "Specify a target to run"
|
||||||
|
|
||||||
# Builds the UI kit
|
# Builds the UI kit
|
||||||
build-uikit:
|
build-joi:
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
cd uikit && yarn config set network-timeout 300000 && yarn install && yarn build
|
cd joi && yarn config set network-timeout 300000 && yarn install && yarn build
|
||||||
else
|
else
|
||||||
cd uikit && yarn install && yarn build
|
cd joi && yarn install && yarn build
|
||||||
endif
|
endif
|
||||||
|
|
||||||
# Installs yarn dependencies and builds core and extensions
|
# Installs yarn dependencies and builds core and extensions
|
||||||
install-and-build: build-uikit
|
install-and-build: build-joi
|
||||||
ifeq ($(OS),Windows_NT)
|
ifeq ($(OS),Windows_NT)
|
||||||
yarn config set network-timeout 300000
|
yarn config set network-timeout 300000
|
||||||
endif
|
endif
|
||||||
|
|||||||
@ -11,6 +11,13 @@ export enum NativeRoute {
|
|||||||
selectDirectory = 'selectDirectory',
|
selectDirectory = 'selectDirectory',
|
||||||
selectFiles = 'selectFiles',
|
selectFiles = 'selectFiles',
|
||||||
relaunch = 'relaunch',
|
relaunch = 'relaunch',
|
||||||
|
setNativeThemeLight = 'setNativeThemeLight',
|
||||||
|
setNativeThemeDark = 'setNativeThemeDark',
|
||||||
|
|
||||||
|
setMinimizeApp = 'setMinimizeApp',
|
||||||
|
setCloseApp = 'setCloseApp',
|
||||||
|
setMaximizeApp = 'setMaximizeApp',
|
||||||
|
showOpenMenu = 'showOpenMenu',
|
||||||
|
|
||||||
hideQuickAskWindow = 'hideQuickAskWindow',
|
hideQuickAskWindow = 'hideQuickAskWindow',
|
||||||
sendQuickAskInput = 'sendQuickAskInput',
|
sendQuickAskInput = 'sendQuickAskInput',
|
||||||
|
|||||||
@ -15,11 +15,16 @@ export type ModelInfo = {
|
|||||||
*/
|
*/
|
||||||
|
|
||||||
export enum InferenceEngine {
|
export enum InferenceEngine {
|
||||||
|
anthropic = 'anthropic',
|
||||||
|
mistral = 'mistral',
|
||||||
|
martian = 'martian',
|
||||||
|
openrouter = 'openrouter',
|
||||||
nitro = 'nitro',
|
nitro = 'nitro',
|
||||||
openai = 'openai',
|
openai = 'openai',
|
||||||
groq = 'groq',
|
groq = 'groq',
|
||||||
triton_trtllm = 'triton_trtllm',
|
triton_trtllm = 'triton_trtllm',
|
||||||
nitro_tensorrt_llm = 'nitro-tensorrt-llm',
|
nitro_tensorrt_llm = 'nitro-tensorrt-llm',
|
||||||
|
cohere = 'cohere',
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ModelArtifact = {
|
export type ModelArtifact = {
|
||||||
|
|||||||
@ -16,11 +16,16 @@ export type ControllerType = 'slider' | 'checkbox' | 'input'
|
|||||||
|
|
||||||
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
export type InputType = 'password' | 'text' | 'email' | 'number' | 'tel' | 'url'
|
||||||
|
|
||||||
|
const InputActions = ['unobscure', 'copy'] as const
|
||||||
|
export type InputActionsTuple = typeof InputActions
|
||||||
|
export type InputAction = InputActionsTuple[number]
|
||||||
|
|
||||||
export type InputComponentProps = {
|
export type InputComponentProps = {
|
||||||
placeholder: string
|
placeholder: string
|
||||||
value: string
|
value: string
|
||||||
type?: InputType
|
type?: InputType
|
||||||
textAlign?: 'left' | 'right'
|
textAlign?: 'left' | 'right'
|
||||||
|
inputActions?: InputAction[]
|
||||||
}
|
}
|
||||||
|
|
||||||
export type SliderComponentProps = {
|
export type SliderComponentProps = {
|
||||||
|
|||||||
@ -1,4 +1,4 @@
|
|||||||
import { app, ipcMain, dialog, shell } from 'electron'
|
import { app, ipcMain, dialog, shell, nativeTheme, screen } from 'electron'
|
||||||
import { join } from 'path'
|
import { join } from 'path'
|
||||||
import { windowManager } from '../managers/window'
|
import { windowManager } from '../managers/window'
|
||||||
import {
|
import {
|
||||||
@ -10,7 +10,10 @@ import {
|
|||||||
NativeRoute,
|
NativeRoute,
|
||||||
SelectFileProp,
|
SelectFileProp,
|
||||||
} from '@janhq/core/node'
|
} from '@janhq/core/node'
|
||||||
import { SelectFileOption } from '@janhq/core/.'
|
import { SelectFileOption } from '@janhq/core'
|
||||||
|
import { menu } from '../utils/menu'
|
||||||
|
|
||||||
|
const isMac = process.platform === 'darwin'
|
||||||
|
|
||||||
export function handleAppIPCs() {
|
export function handleAppIPCs() {
|
||||||
/**
|
/**
|
||||||
@ -22,6 +25,41 @@ export function handleAppIPCs() {
|
|||||||
shell.openPath(getJanDataFolderPath())
|
shell.openPath(getJanDataFolderPath())
|
||||||
})
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the "setNativeThemeLight" IPC message by setting the native theme source to "light".
|
||||||
|
* This will change the appearance of the app to the light theme.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(NativeRoute.setNativeThemeLight, () => {
|
||||||
|
nativeTheme.themeSource = 'light'
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.setCloseApp, () => {
|
||||||
|
windowManager.mainWindow?.close()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.setMinimizeApp, () => {
|
||||||
|
windowManager.mainWindow?.minimize()
|
||||||
|
})
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.setMaximizeApp, async () => {
|
||||||
|
if (windowManager.mainWindow?.isMaximized()) {
|
||||||
|
// const bounds = await getBounds()
|
||||||
|
// windowManager.mainWindow?.setSize(bounds.width, bounds.height)
|
||||||
|
// windowManager.mainWindow?.setPosition(Number(bounds.x), Number(bounds.y))
|
||||||
|
windowManager.mainWindow.restore()
|
||||||
|
} else {
|
||||||
|
windowManager.mainWindow?.maximize()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles the "setNativeThemeDark" IPC message by setting the native theme source to "dark".
|
||||||
|
* This will change the appearance of the app to the dark theme.
|
||||||
|
*/
|
||||||
|
ipcMain.handle(NativeRoute.setNativeThemeDark, () => {
|
||||||
|
nativeTheme.themeSource = 'dark'
|
||||||
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Opens a URL in the user's default browser.
|
* Opens a URL in the user's default browser.
|
||||||
* @param _event - The IPC event object.
|
* @param _event - The IPC event object.
|
||||||
@ -136,6 +174,16 @@ export function handleAppIPCs() {
|
|||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
ipcMain.handle(NativeRoute.showOpenMenu, function (e, args) {
|
||||||
|
if (!isMac && windowManager.mainWindow) {
|
||||||
|
menu.popup({
|
||||||
|
window: windowManager.mainWindow,
|
||||||
|
x: args.x,
|
||||||
|
y: args.y,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
ipcMain.handle(
|
ipcMain.handle(
|
||||||
NativeRoute.hideMainWindow,
|
NativeRoute.hideMainWindow,
|
||||||
async (): Promise<void> => windowManager.hideMainWindow()
|
async (): Promise<void> => windowManager.hideMainWindow()
|
||||||
|
|||||||
@ -19,7 +19,7 @@ import { handleAppIPCs } from './handlers/native'
|
|||||||
**/
|
**/
|
||||||
import { setupMenu } from './utils/menu'
|
import { setupMenu } from './utils/menu'
|
||||||
import { createUserSpace } from './utils/path'
|
import { createUserSpace } from './utils/path'
|
||||||
import { migrateExtensions } from './utils/migration'
|
import { migrate } from './utils/migration'
|
||||||
import { cleanUpAndQuit } from './utils/clean'
|
import { cleanUpAndQuit } from './utils/clean'
|
||||||
import { setupExtensions } from './utils/extension'
|
import { setupExtensions } from './utils/extension'
|
||||||
import { setupCore } from './utils/setup'
|
import { setupCore } from './utils/setup'
|
||||||
@ -79,7 +79,7 @@ app
|
|||||||
})
|
})
|
||||||
.then(setupCore)
|
.then(setupCore)
|
||||||
.then(createUserSpace)
|
.then(createUserSpace)
|
||||||
.then(migrateExtensions)
|
.then(migrate)
|
||||||
.then(setupExtensions)
|
.then(setupExtensions)
|
||||||
.then(setupMenu)
|
.then(setupMenu)
|
||||||
.then(handleIPCs)
|
.then(handleIPCs)
|
||||||
|
|||||||
@ -1,17 +1,17 @@
|
|||||||
const DEFAULT_WIDTH = 1200
|
|
||||||
const DEFAULT_MIN_WIDTH = 400
|
const DEFAULT_MIN_WIDTH = 400
|
||||||
const DEFAULT_HEIGHT = 800
|
|
||||||
|
|
||||||
export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
export const mainWindowConfig: Electron.BrowserWindowConstructorOptions = {
|
||||||
width: DEFAULT_WIDTH,
|
|
||||||
minWidth: DEFAULT_MIN_WIDTH,
|
|
||||||
height: DEFAULT_HEIGHT,
|
|
||||||
skipTaskbar: false,
|
skipTaskbar: false,
|
||||||
|
minWidth: DEFAULT_MIN_WIDTH,
|
||||||
show: true,
|
show: true,
|
||||||
|
titleBarStyle: 'hidden',
|
||||||
|
vibrancy: 'fullscreen-ui',
|
||||||
|
visualEffectState: 'active',
|
||||||
|
backgroundMaterial: 'acrylic',
|
||||||
|
maximizable: false,
|
||||||
|
autoHideMenuBar: true,
|
||||||
trafficLightPosition: {
|
trafficLightPosition: {
|
||||||
x: 10,
|
x: 16,
|
||||||
y: 15,
|
y: 10,
|
||||||
},
|
},
|
||||||
titleBarStyle: 'hiddenInset',
|
|
||||||
vibrancy: 'sidebar',
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -2,12 +2,14 @@ import { BrowserWindow, app, shell } from 'electron'
|
|||||||
import { quickAskWindowConfig } from './quickAskWindowConfig'
|
import { quickAskWindowConfig } from './quickAskWindowConfig'
|
||||||
import { mainWindowConfig } from './mainWindowConfig'
|
import { mainWindowConfig } from './mainWindowConfig'
|
||||||
import { getAppConfigurations, AppEvent } from '@janhq/core/node'
|
import { getAppConfigurations, AppEvent } from '@janhq/core/node'
|
||||||
|
import { getBounds, saveBounds } from '../utils/setup'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Manages the current window instance.
|
* Manages the current window instance.
|
||||||
*/
|
*/
|
||||||
// TODO: refactor this
|
// TODO: refactor this
|
||||||
let isAppQuitting = false
|
let isAppQuitting = false
|
||||||
|
|
||||||
class WindowManager {
|
class WindowManager {
|
||||||
public mainWindow?: BrowserWindow
|
public mainWindow?: BrowserWindow
|
||||||
private _quickAskWindow: BrowserWindow | undefined = undefined
|
private _quickAskWindow: BrowserWindow | undefined = undefined
|
||||||
@ -19,9 +21,15 @@ class WindowManager {
|
|||||||
* Creates a new window instance.
|
* Creates a new window instance.
|
||||||
* @returns The created window instance.
|
* @returns The created window instance.
|
||||||
*/
|
*/
|
||||||
createMainWindow(preloadPath: string, startUrl: string) {
|
async createMainWindow(preloadPath: string, startUrl: string) {
|
||||||
|
const bounds = await getBounds()
|
||||||
|
|
||||||
this.mainWindow = new BrowserWindow({
|
this.mainWindow = new BrowserWindow({
|
||||||
...mainWindowConfig,
|
...mainWindowConfig,
|
||||||
|
width: bounds.width,
|
||||||
|
height: bounds.height,
|
||||||
|
x: bounds.x,
|
||||||
|
y: bounds.y,
|
||||||
webPreferences: {
|
webPreferences: {
|
||||||
nodeIntegration: true,
|
nodeIntegration: true,
|
||||||
preload: preloadPath,
|
preload: preloadPath,
|
||||||
@ -40,6 +48,14 @@ class WindowManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
this.mainWindow.on('resized', () => {
|
||||||
|
saveBounds(this.mainWindow?.getBounds())
|
||||||
|
})
|
||||||
|
|
||||||
|
this.mainWindow.on('moved', () => {
|
||||||
|
saveBounds(this.mainWindow?.getBounds())
|
||||||
|
})
|
||||||
|
|
||||||
/* Load frontend app to the window */
|
/* Load frontend app to the window */
|
||||||
this.mainWindow.loadURL(startUrl)
|
this.mainWindow.loadURL(startUrl)
|
||||||
|
|
||||||
|
|||||||
@ -14,15 +14,19 @@
|
|||||||
"renderer/**/*",
|
"renderer/**/*",
|
||||||
"build/**/*.{js,map}",
|
"build/**/*.{js,map}",
|
||||||
"pre-install",
|
"pre-install",
|
||||||
|
"themes",
|
||||||
"docs/**/*",
|
"docs/**/*",
|
||||||
"scripts/**/*",
|
"scripts/**/*",
|
||||||
"icons/**/*"
|
"icons/**/*",
|
||||||
|
"themes"
|
||||||
],
|
],
|
||||||
"asarUnpack": [
|
"asarUnpack": [
|
||||||
"pre-install",
|
"pre-install",
|
||||||
|
"themes",
|
||||||
"docs",
|
"docs",
|
||||||
"scripts",
|
"scripts",
|
||||||
"icons"
|
"icons",
|
||||||
|
"themes"
|
||||||
],
|
],
|
||||||
"publish": [
|
"publish": [
|
||||||
{
|
{
|
||||||
@ -114,7 +118,7 @@
|
|||||||
"@types/request": "^2.48.12",
|
"@types/request": "^2.48.12",
|
||||||
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
"@typescript-eslint/eslint-plugin": "^6.7.3",
|
||||||
"@typescript-eslint/parser": "^6.7.3",
|
"@typescript-eslint/parser": "^6.7.3",
|
||||||
"electron": "28.0.0",
|
"electron": "30.0.6",
|
||||||
"electron-builder": "^24.13.3",
|
"electron-builder": "^24.13.3",
|
||||||
"electron-builder-squirrel-windows": "^24.13.3",
|
"electron-builder-squirrel-windows": "^24.13.3",
|
||||||
"electron-devtools-installer": "^3.2.0",
|
"electron-devtools-installer": "^3.2.0",
|
||||||
|
|||||||
@ -112,7 +112,8 @@ const template: (Electron.MenuItemConstructorOptions | Electron.MenuItem)[] = [
|
|||||||
},
|
},
|
||||||
]
|
]
|
||||||
|
|
||||||
|
export const menu = Menu.buildFromTemplate(template)
|
||||||
|
|
||||||
export const setupMenu = () => {
|
export const setupMenu = () => {
|
||||||
const menu = Menu.buildFromTemplate(template)
|
|
||||||
Menu.setApplicationMenu(menu)
|
Menu.setApplicationMenu(menu)
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,29 +1,87 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
|
||||||
import { rmdir } from 'fs'
|
import { join } from 'path'
|
||||||
|
import {
|
||||||
|
rmdirSync,
|
||||||
|
readFileSync,
|
||||||
|
existsSync,
|
||||||
|
mkdirSync,
|
||||||
|
readdirSync,
|
||||||
|
cpSync,
|
||||||
|
lstatSync,
|
||||||
|
} from 'fs'
|
||||||
import Store from 'electron-store'
|
import Store from 'electron-store'
|
||||||
import { getJanExtensionsPath } from '@janhq/core/node'
|
import {
|
||||||
|
getJanExtensionsPath,
|
||||||
|
getJanDataFolderPath,
|
||||||
|
appResourcePath,
|
||||||
|
} from '@janhq/core/node'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Migrates the extensions by deleting the `extensions` directory in the user data path.
|
* Migrates the extensions & themes.
|
||||||
* If the `migrated_version` key in the `Store` object does not match the current app version,
|
* If the `migrated_version` key in the `Store` object does not match the current app version,
|
||||||
* the function deletes the `extensions` directory and sets the `migrated_version` key to the current app version.
|
* the function deletes the `extensions` directory and sets the `migrated_version` key to the current app version.
|
||||||
* @returns A Promise that resolves when the migration is complete.
|
* @returns A Promise that resolves when the migration is complete.
|
||||||
*/
|
*/
|
||||||
export function migrateExtensions() {
|
export async function migrate() {
|
||||||
return new Promise((resolve) => {
|
|
||||||
const store = new Store()
|
const store = new Store()
|
||||||
if (store.get('migrated_version') !== app.getVersion()) {
|
if (store.get('migrated_version') !== app.getVersion()) {
|
||||||
console.debug('start migration:', store.get('migrated_version'))
|
console.debug('start migration:', store.get('migrated_version'))
|
||||||
|
|
||||||
rmdir(getJanExtensionsPath(), { recursive: true }, function (err) {
|
// if (existsSync(getJanExtensionsPath()))
|
||||||
if (err) console.error(err)
|
// rmdirSync(getJanExtensionsPath(), { recursive: true })
|
||||||
|
await migrateThemes()
|
||||||
|
|
||||||
store.set('migrated_version', app.getVersion())
|
store.set('migrated_version', app.getVersion())
|
||||||
console.debug('migrate extensions done')
|
console.debug('migrate extensions done')
|
||||||
resolve(undefined)
|
} else if (!existsSync(join(getJanDataFolderPath(), 'themes'))) {
|
||||||
})
|
await migrateThemes()
|
||||||
} else {
|
}
|
||||||
resolve(undefined)
|
}
|
||||||
|
|
||||||
|
async function migrateThemes() {
|
||||||
|
if (!existsSync(join(getJanDataFolderPath(), 'themes')))
|
||||||
|
mkdirSync(join(getJanDataFolderPath(), 'themes'), { recursive: true })
|
||||||
|
|
||||||
|
const themes = readdirSync(join(await appResourcePath(), 'themes'))
|
||||||
|
for (const theme of themes) {
|
||||||
|
const themePath = join(await appResourcePath(), 'themes', theme)
|
||||||
|
if (existsSync(themePath) && !lstatSync(themePath).isDirectory()) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
await checkAndMigrateTheme(theme, themePath)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function checkAndMigrateTheme(
|
||||||
|
sourceThemeName: string,
|
||||||
|
sourceThemePath: string
|
||||||
|
) {
|
||||||
|
const janDataThemesFolder = join(getJanDataFolderPath(), 'themes')
|
||||||
|
const existingTheme = readdirSync(janDataThemesFolder).find(
|
||||||
|
(theme) => theme === sourceThemeName
|
||||||
|
)
|
||||||
|
if (existingTheme) {
|
||||||
|
const desTheme = join(janDataThemesFolder, existingTheme)
|
||||||
|
if (!existsSync(desTheme) || !lstatSync(desTheme).isDirectory()) return
|
||||||
|
|
||||||
|
const desThemeData = JSON.parse(
|
||||||
|
readFileSync(join(desTheme, 'theme.json'), 'utf-8')
|
||||||
|
)
|
||||||
|
const sourceThemeData = JSON.parse(
|
||||||
|
readFileSync(join(sourceThemePath, 'theme.json'), 'utf-8')
|
||||||
|
)
|
||||||
|
if (desThemeData.version !== sourceThemeData.version) {
|
||||||
|
console.debug('Updating theme', existingTheme)
|
||||||
|
rmdirSync(desTheme, { recursive: true })
|
||||||
|
cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
console.debug('Adding new theme', sourceThemeName)
|
||||||
|
cpSync(sourceThemePath, join(janDataThemesFolder, sourceThemeName), {
|
||||||
|
recursive: true,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,4 +1,10 @@
|
|||||||
import { app } from 'electron'
|
import { app } from 'electron'
|
||||||
|
import Store from 'electron-store'
|
||||||
|
|
||||||
|
const DEFAULT_WIDTH = 1000
|
||||||
|
const DEFAULT_HEIGHT = 800
|
||||||
|
|
||||||
|
const storage = new Store()
|
||||||
|
|
||||||
export const setupCore = async () => {
|
export const setupCore = async () => {
|
||||||
// Setup core api for main process
|
// Setup core api for main process
|
||||||
@ -7,3 +13,24 @@ export const setupCore = async () => {
|
|||||||
appPath: () => app.getPath('userData'),
|
appPath: () => app.getPath('userData'),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export const getBounds = async () => {
|
||||||
|
const defaultBounds = {
|
||||||
|
x: undefined,
|
||||||
|
y: undefined,
|
||||||
|
width: DEFAULT_WIDTH,
|
||||||
|
height: DEFAULT_HEIGHT,
|
||||||
|
}
|
||||||
|
|
||||||
|
const bounds = await storage.get('windowBounds')
|
||||||
|
if (bounds) {
|
||||||
|
return bounds as Electron.Rectangle
|
||||||
|
} else {
|
||||||
|
storage.set('windowBounds', defaultBounds)
|
||||||
|
return defaultBounds
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const saveBounds = (bounds: Electron.Rectangle | undefined) => {
|
||||||
|
storage.set('windowBounds', bounds)
|
||||||
|
}
|
||||||
|
|||||||
@ -1 +1 @@
|
|||||||
0.4.9
|
0.4.11
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"name": "@janhq/inference-cortex-extension",
|
"name": "@janhq/inference-cortex-extension",
|
||||||
"productName": "Cortex Inference Engine",
|
"productName": "Cortex Inference Engine",
|
||||||
"version": "1.0.10",
|
"version": "1.0.11",
|
||||||
"description": "This extension embeds cortex.cpp, a lightweight inference engine written in C++. See https://nitro.jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.",
|
"description": "This extension embeds cortex.cpp, a lightweight inference engine written in C++. See https://nitro.jan.ai.\nAdditional dependencies could be installed to run without Cuda Toolkit installation.",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"node": "dist/node/index.cjs.js",
|
"node": "dist/node/index.cjs.js",
|
||||||
|
|||||||
@ -0,0 +1,36 @@
|
|||||||
|
{
|
||||||
|
"sources": [
|
||||||
|
{
|
||||||
|
"filename": "Codestral-22B-v0.1-Q4_K_M.gguf",
|
||||||
|
"url": "https://huggingface.co/bartowski/Codestral-22B-v0.1-GGUF/resolve/main/Codestral-22B-v0.1-Q4_K_M.gguf"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"id": "codestral-22b",
|
||||||
|
"object": "model",
|
||||||
|
"name": "Codestral 22B Q4",
|
||||||
|
"version": "1.0",
|
||||||
|
"description": "Latest model from MistralAI optimized for code generation tasks.",
|
||||||
|
"format": "gguf",
|
||||||
|
"settings": {
|
||||||
|
"ctx_len": 32000,
|
||||||
|
"prompt_template": "{system_message} [INST] {prompt} [/INST]",
|
||||||
|
"llama_model_path": "Codestral-22B-v0.1-Q4_K_M.gguf",
|
||||||
|
"ngl": 56
|
||||||
|
},
|
||||||
|
"parameters": {
|
||||||
|
"temperature": 0.7,
|
||||||
|
"top_p": 0.95,
|
||||||
|
"stream": true,
|
||||||
|
"max_tokens": 32000,
|
||||||
|
"stop": ["<endofstring>, [/INST]"],
|
||||||
|
"frequency_penalty": 0,
|
||||||
|
"presence_penalty": 0
|
||||||
|
},
|
||||||
|
"metadata": {
|
||||||
|
"author": "MistralAI",
|
||||||
|
"tags": ["22B", "Finetuned", "Featured"],
|
||||||
|
"size": 13341237440
|
||||||
|
},
|
||||||
|
"engine": "nitro"
|
||||||
|
}
|
||||||
|
|
||||||
@ -1,20 +1,20 @@
|
|||||||
{
|
{
|
||||||
"sources": [
|
"sources": [
|
||||||
{
|
{
|
||||||
"filename": "mistral-7b-instruct-v0.2.Q4_K_M.gguf",
|
"filename": "Mistral-7B-Instruct-v0.3-Q4_K_M.gguf",
|
||||||
"url": "https://huggingface.co/TheBloke/Mistral-7B-Instruct-v0.2-GGUF/resolve/main/mistral-7b-instruct-v0.2.Q4_K_M.gguf"
|
"url": "https://huggingface.co/bartowski/Mistral-7B-Instruct-v0.3-GGUF/resolve/main/Mistral-7B-Instruct-v0.3-Q4_K_M.gguf"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"id": "mistral-ins-7b-q4",
|
"id": "mistral-ins-7b-q4",
|
||||||
"object": "model",
|
"object": "model",
|
||||||
"name": "Mistral Instruct 7B Q4",
|
"name": "Mistral Instruct 7B Q4",
|
||||||
"version": "1.1",
|
"version": "1.2",
|
||||||
"description": "Mistral Instruct 7b model, specifically designed for a comprehensive understanding of the world.",
|
"description": "Mistral Instruct 7b model, specifically designed for a comprehensive understanding of the world.",
|
||||||
"format": "gguf",
|
"format": "gguf",
|
||||||
"settings": {
|
"settings": {
|
||||||
"ctx_len": 32768,
|
"ctx_len": 32768,
|
||||||
"prompt_template": "[INST] {prompt} [/INST]",
|
"prompt_template": "{system_message} [INST] {prompt} [/INST]",
|
||||||
"llama_model_path": "mistral-7b-instruct-v0.2.Q4_K_M.gguf",
|
"llama_model_path": "Mistral-7B-Instruct-v0.3-Q4_K_M.gguf",
|
||||||
"ngl": 32
|
"ngl": 32
|
||||||
},
|
},
|
||||||
"parameters": {
|
"parameters": {
|
||||||
|
|||||||
@ -8,7 +8,7 @@
|
|||||||
"id": "phi3-medium",
|
"id": "phi3-medium",
|
||||||
"object": "model",
|
"object": "model",
|
||||||
"name": "Phi-3 Medium",
|
"name": "Phi-3 Medium",
|
||||||
"version": "1.0",
|
"version": "1.1",
|
||||||
"description": "Phi-3 Medium is Microsoft's latest SOTA model.",
|
"description": "Phi-3 Medium is Microsoft's latest SOTA model.",
|
||||||
"format": "gguf",
|
"format": "gguf",
|
||||||
"settings": {
|
"settings": {
|
||||||
@ -29,7 +29,7 @@
|
|||||||
"metadata": {
|
"metadata": {
|
||||||
"author": "Microsoft",
|
"author": "Microsoft",
|
||||||
"tags": [
|
"tags": [
|
||||||
"7B",
|
"14B",
|
||||||
"Finetuned"
|
"Finetuned"
|
||||||
],
|
],
|
||||||
"size": 8366000000
|
"size": 8366000000
|
||||||
|
|||||||
@ -38,6 +38,7 @@ const llama3Hermes8bJson = require('./resources/models/llama3-hermes-8b/model.js
|
|||||||
const aya8bJson = require('./resources/models/aya-23-8b/model.json')
|
const aya8bJson = require('./resources/models/aya-23-8b/model.json')
|
||||||
const aya35bJson = require('./resources/models/aya-23-35b/model.json')
|
const aya35bJson = require('./resources/models/aya-23-35b/model.json')
|
||||||
const phimediumJson = require('./resources/models/phi3-medium/model.json')
|
const phimediumJson = require('./resources/models/phi3-medium/model.json')
|
||||||
|
const codestralJson = require('./resources/models/codestral-22b/model.json')
|
||||||
|
|
||||||
export default [
|
export default [
|
||||||
{
|
{
|
||||||
@ -82,7 +83,8 @@ export default [
|
|||||||
llama3Hermes8bJson,
|
llama3Hermes8bJson,
|
||||||
phimediumJson,
|
phimediumJson,
|
||||||
aya8bJson,
|
aya8bJson,
|
||||||
aya35bJson
|
aya35bJson,
|
||||||
|
codestralJson
|
||||||
]),
|
]),
|
||||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||||
DEFAULT_SETTINGS: JSON.stringify(defaultSettingJson),
|
DEFAULT_SETTINGS: JSON.stringify(defaultSettingJson),
|
||||||
|
|||||||
@ -23,7 +23,7 @@
|
|||||||
"top_p": 0.95,
|
"top_p": 0.95,
|
||||||
"stream": true,
|
"stream": true,
|
||||||
"max_tokens": 2048,
|
"max_tokens": 2048,
|
||||||
"stop": ["<endofstring>"],
|
"stop": ["<|END_OF_TURN_TOKEN|>", "<end_of_turn>", "[/INST]", "<|end_of_text|>", "<|eot_id|>", "<|im_end|>", "<|end|>"],
|
||||||
"frequency_penalty": 0,
|
"frequency_penalty": 0,
|
||||||
"presence_penalty": 0
|
"presence_penalty": 0
|
||||||
},
|
},
|
||||||
|
|||||||
14
extensions/model-extension/resources/settings.json
Normal file
14
extensions/model-extension/resources/settings.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
[
|
||||||
|
{
|
||||||
|
"key": "hugging-face-access-token",
|
||||||
|
"title": "Hugging Face Access Token",
|
||||||
|
"description": "Access tokens programmatically authenticate your identity to the Hugging Face Hub, allowing applications to perform specific actions specified by the scope of permissions granted.",
|
||||||
|
"controllerType": "input",
|
||||||
|
"controllerProps": {
|
||||||
|
"value": "",
|
||||||
|
"placeholder": "hf_**********************************",
|
||||||
|
"type": "password",
|
||||||
|
"inputActions": ["unobscure", "copy"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
@ -4,6 +4,7 @@ import typescript from 'rollup-plugin-typescript2'
|
|||||||
import json from '@rollup/plugin-json'
|
import json from '@rollup/plugin-json'
|
||||||
import replace from '@rollup/plugin-replace'
|
import replace from '@rollup/plugin-replace'
|
||||||
|
|
||||||
|
const settingJson = require('./resources/settings.json')
|
||||||
const packageJson = require('./package.json')
|
const packageJson = require('./package.json')
|
||||||
const defaultModelJson = require('./resources/default-model.json')
|
const defaultModelJson = require('./resources/default-model.json')
|
||||||
|
|
||||||
@ -20,6 +21,7 @@ export default [
|
|||||||
replace({
|
replace({
|
||||||
preventAssignment: true,
|
preventAssignment: true,
|
||||||
DEFAULT_MODEL: JSON.stringify(defaultModelJson),
|
DEFAULT_MODEL: JSON.stringify(defaultModelJson),
|
||||||
|
SETTINGS: JSON.stringify(settingJson),
|
||||||
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
NODE: JSON.stringify(`${packageJson.name}/${packageJson.node}`),
|
||||||
}),
|
}),
|
||||||
// Allow json resolution
|
// Allow json resolution
|
||||||
|
|||||||
@ -31,6 +31,11 @@ import { GGUFMetadata, gguf } from '@huggingface/gguf'
|
|||||||
import { NotSupportedModelError } from './@types/NotSupportModelError'
|
import { NotSupportedModelError } from './@types/NotSupportModelError'
|
||||||
import { InvalidHostError } from './@types/InvalidHostError'
|
import { InvalidHostError } from './@types/InvalidHostError'
|
||||||
|
|
||||||
|
declare const SETTINGS: Array<any>
|
||||||
|
enum Settings {
|
||||||
|
huggingFaceAccessToken = 'hugging-face-access-token',
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A extension for models
|
* A extension for models
|
||||||
*/
|
*/
|
||||||
@ -63,6 +68,7 @@ export default class JanModelExtension extends ModelExtension {
|
|||||||
*/
|
*/
|
||||||
async onLoad() {
|
async onLoad() {
|
||||||
// Handle Desktop Events
|
// Handle Desktop Events
|
||||||
|
this.registerSettings(SETTINGS)
|
||||||
this.handleDesktopEvents()
|
this.handleDesktopEvents()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,7 +201,21 @@ export default class JanModelExtension extends ModelExtension {
|
|||||||
const sanitizedUrl = this.toHuggingFaceUrl(repoId)
|
const sanitizedUrl = this.toHuggingFaceUrl(repoId)
|
||||||
console.debug('sanitizedUrl', sanitizedUrl)
|
console.debug('sanitizedUrl', sanitizedUrl)
|
||||||
|
|
||||||
const res = await fetch(sanitizedUrl)
|
const huggingFaceAccessToken = (
|
||||||
|
await this.getSetting<string>(Settings.huggingFaceAccessToken, '')
|
||||||
|
).trim()
|
||||||
|
|
||||||
|
const headers = {
|
||||||
|
Accept: 'application/json',
|
||||||
|
}
|
||||||
|
|
||||||
|
if (huggingFaceAccessToken.length > 0) {
|
||||||
|
headers['Authorization'] = `Bearer ${huggingFaceAccessToken}`
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await fetch(sanitizedUrl, {
|
||||||
|
headers: headers,
|
||||||
|
})
|
||||||
const response = await res.json()
|
const response = await res.json()
|
||||||
if (response['error'] != null) {
|
if (response['error'] != null) {
|
||||||
throw new Error(response['error'])
|
throw new Error(response['error'])
|
||||||
|
|||||||
@ -3,3 +3,4 @@ node_modules/
|
|||||||
dist/
|
dist/
|
||||||
*.hbs
|
*.hbs
|
||||||
*.mdx
|
*.mdx
|
||||||
|
*.mjs
|
||||||
13
joi/README.md
Normal file
13
joi/README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# @janhq/joi
|
||||||
|
|
||||||
|
To install dependencies:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn install
|
||||||
|
```
|
||||||
|
|
||||||
|
To run:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
yarn run dev
|
||||||
|
```
|
||||||
59
joi/package.json
Normal file
59
joi/package.json
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
{
|
||||||
|
"name": "@janhq/joi",
|
||||||
|
"version": "0.0.0",
|
||||||
|
"main": "dist/cjs/index.js",
|
||||||
|
"module": "dist/esm/index.js",
|
||||||
|
"types": "dist/index.d.ts",
|
||||||
|
"description": "A collection of UI component",
|
||||||
|
"private": true,
|
||||||
|
"files": [
|
||||||
|
"dist"
|
||||||
|
],
|
||||||
|
"keywords": [
|
||||||
|
"design-system"
|
||||||
|
],
|
||||||
|
"license": "MIT",
|
||||||
|
"homepage": "https://github.com/codecentrum/piksel#readme",
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/codecentrum/piksel.git"
|
||||||
|
},
|
||||||
|
"bugs": "https://github.com/codecentrum/piksel/issues",
|
||||||
|
"scripts": {
|
||||||
|
"dev": "rollup -c -w",
|
||||||
|
"build": "rimraf ./dist && rollup -c"
|
||||||
|
},
|
||||||
|
"peerDependencies": {
|
||||||
|
"class-variance-authority": "^0.7.0",
|
||||||
|
"react": "^18",
|
||||||
|
"typescript": "^5.0.0"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"@radix-ui/react-accordion": "^1.1.2",
|
||||||
|
"@radix-ui/react-dialog": "^1.0.5",
|
||||||
|
"@radix-ui/react-icons": "^1.3.0",
|
||||||
|
"@radix-ui/react-scroll-area": "^1.0.5",
|
||||||
|
"@radix-ui/react-select": "^2.0.0",
|
||||||
|
"@radix-ui/react-slider": "^1.1.2",
|
||||||
|
"@radix-ui/react-slot": "^1.0.2",
|
||||||
|
"@radix-ui/react-tabs": "^1.0.4",
|
||||||
|
"@radix-ui/react-tooltip": "^1.0.7",
|
||||||
|
"tailwind-merge": "^2.2.0",
|
||||||
|
"autoprefixer": "10.4.16",
|
||||||
|
"tailwindcss": "^3.4.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@rollup/plugin-node-resolve": "^15.2.3",
|
||||||
|
"@rollup/plugin-terser": "^0.4.4",
|
||||||
|
"prettier": "^3.0.3",
|
||||||
|
"prettier-plugin-tailwindcss": "^0.5.6",
|
||||||
|
"rollup": "^4.12.0",
|
||||||
|
"rollup-plugin-bundle-size": "^1.0.3",
|
||||||
|
"rollup-plugin-commonjs": "^10.1.0",
|
||||||
|
"rollup-plugin-copy": "^3.5.0",
|
||||||
|
"rollup-plugin-dts": "^6.1.0",
|
||||||
|
"rollup-plugin-peer-deps-external": "^2.2.4",
|
||||||
|
"rollup-plugin-postcss": "^4.0.2",
|
||||||
|
"rollup-plugin-typescript2": "^0.36.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
73
joi/rollup.config.mjs
Normal file
73
joi/rollup.config.mjs
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
import { readFileSync } from 'fs'
|
||||||
|
import dts from 'rollup-plugin-dts'
|
||||||
|
import terser from '@rollup/plugin-terser'
|
||||||
|
import autoprefixer from 'autoprefixer'
|
||||||
|
import commonjs from 'rollup-plugin-commonjs'
|
||||||
|
import bundleSize from 'rollup-plugin-bundle-size'
|
||||||
|
import peerDepsExternal from 'rollup-plugin-peer-deps-external'
|
||||||
|
import postcss from 'rollup-plugin-postcss'
|
||||||
|
import typescript from 'rollup-plugin-typescript2'
|
||||||
|
import tailwindcss from 'tailwindcss'
|
||||||
|
import typescriptEngine from 'typescript'
|
||||||
|
import resolve from '@rollup/plugin-node-resolve'
|
||||||
|
import copy from 'rollup-plugin-copy'
|
||||||
|
|
||||||
|
const packageJson = JSON.parse(readFileSync('./package.json'))
|
||||||
|
|
||||||
|
import tailwindConfig from './tailwind.config.js'
|
||||||
|
|
||||||
|
export default [
|
||||||
|
{
|
||||||
|
input: `./src/index.ts`,
|
||||||
|
output: [
|
||||||
|
{
|
||||||
|
file: packageJson.main,
|
||||||
|
format: 'cjs',
|
||||||
|
sourcemap: false,
|
||||||
|
exports: 'named',
|
||||||
|
name: packageJson.name,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
file: packageJson.module,
|
||||||
|
format: 'es',
|
||||||
|
exports: 'named',
|
||||||
|
sourcemap: false,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
plugins: [
|
||||||
|
postcss({
|
||||||
|
plugins: [autoprefixer(), tailwindcss(tailwindConfig)],
|
||||||
|
sourceMap: true,
|
||||||
|
use: ['sass'],
|
||||||
|
minimize: true,
|
||||||
|
extract: 'main.css',
|
||||||
|
}),
|
||||||
|
peerDepsExternal({ includeDependencies: true }),
|
||||||
|
resolve(),
|
||||||
|
commonjs(),
|
||||||
|
typescript({
|
||||||
|
tsconfig: './tsconfig.json',
|
||||||
|
typescript: typescriptEngine,
|
||||||
|
sourceMap: false,
|
||||||
|
exclude: ['docs', 'dist', 'node_modules/**'],
|
||||||
|
}),
|
||||||
|
terser(),
|
||||||
|
],
|
||||||
|
watch: {
|
||||||
|
clearScreen: false,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
input: 'dist/esm/index.d.ts',
|
||||||
|
output: [{ file: 'dist/index.d.ts', format: 'esm' }],
|
||||||
|
external: [/\.(sc|sa|c)ss$/],
|
||||||
|
plugins: [
|
||||||
|
dts(),
|
||||||
|
peerDepsExternal({ includeDependencies: true }),
|
||||||
|
copy({
|
||||||
|
targets: [{ src: 'dist/esm/main.css', dest: 'dist' }],
|
||||||
|
}),
|
||||||
|
bundleSize(),
|
||||||
|
],
|
||||||
|
},
|
||||||
|
]
|
||||||
45
joi/src/core/Accordion/index.tsx
Normal file
45
joi/src/core/Accordion/index.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
import * as AccordionPrimitive from '@radix-ui/react-accordion'
|
||||||
|
|
||||||
|
import { ChevronDownIcon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
type AccordionProps = {
|
||||||
|
defaultValue: string[]
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
type AccordionItemProps = {
|
||||||
|
children: ReactNode
|
||||||
|
value: string
|
||||||
|
title: string
|
||||||
|
}
|
||||||
|
|
||||||
|
const AccordionItem = ({ children, value, title }: AccordionItemProps) => {
|
||||||
|
return (
|
||||||
|
<AccordionPrimitive.Item className="accordion__item" value={value}>
|
||||||
|
<AccordionPrimitive.Header className="accordion__header">
|
||||||
|
<AccordionPrimitive.Trigger className="accordion__trigger">
|
||||||
|
<h6>{title}</h6>
|
||||||
|
<ChevronDownIcon className="accordion__chevron" aria-hidden />
|
||||||
|
</AccordionPrimitive.Trigger>
|
||||||
|
</AccordionPrimitive.Header>
|
||||||
|
<AccordionPrimitive.Content className="accordion__content">
|
||||||
|
<div className="accordion__content--wrapper">{children}</div>
|
||||||
|
</AccordionPrimitive.Content>
|
||||||
|
</AccordionPrimitive.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Accordion = ({ defaultValue, children }: AccordionProps) => (
|
||||||
|
<AccordionPrimitive.Root
|
||||||
|
className="accordion"
|
||||||
|
type="multiple"
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</AccordionPrimitive.Root>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Accordion, AccordionItem }
|
||||||
73
joi/src/core/Accordion/styles.scss
Normal file
73
joi/src/core/Accordion/styles.scss
Normal file
@ -0,0 +1,73 @@
|
|||||||
|
.accordion {
|
||||||
|
border-top: 1px solid hsla(var(--app-border));
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
overflow: hidden;
|
||||||
|
margin-top: 1px;
|
||||||
|
border-bottom: 1px solid hsla(var(--app-border));
|
||||||
|
|
||||||
|
:focus-within {
|
||||||
|
position: relative;
|
||||||
|
z-index: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
display: flex;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__trigger {
|
||||||
|
font-family: inherit;
|
||||||
|
background-color: transparent;
|
||||||
|
padding: 0 16px;
|
||||||
|
height: 40px;
|
||||||
|
flex: 1;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
overflow: hidden;
|
||||||
|
|
||||||
|
&--wrapper {
|
||||||
|
padding: 4px 16px 16px 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__chevron {
|
||||||
|
color: hsla(var(--text-secondary));
|
||||||
|
transition: transform 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion__content[data-state='open'] {
|
||||||
|
animation: slideDown 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion__content[data-state='closed'] {
|
||||||
|
animation: slideUp 300ms cubic-bezier(0.87, 0, 0.13, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
.accordion__trigger[data-state='open'] > .accordion__chevron {
|
||||||
|
transform: rotate(180deg);
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDown {
|
||||||
|
from {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUp {
|
||||||
|
from {
|
||||||
|
height: var(--radix-accordion-content-height);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
height: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
50
joi/src/core/Badge/index.tsx
Normal file
50
joi/src/core/Badge/index.tsx
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import React, { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
const badgeVariants = cva('badge', {
|
||||||
|
variants: {
|
||||||
|
theme: {
|
||||||
|
primary: 'badge--primary',
|
||||||
|
secondary: 'badge--secondary',
|
||||||
|
warning: 'badge--warning',
|
||||||
|
success: 'badge--success',
|
||||||
|
info: 'badge--info',
|
||||||
|
destructive: 'badge--destructive',
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
solid: 'badge--solid',
|
||||||
|
soft: 'badge--soft',
|
||||||
|
outline: 'badge--outline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: 'badge--small',
|
||||||
|
medium: 'badge--medium',
|
||||||
|
large: 'badge--large',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
theme: 'primary',
|
||||||
|
size: 'medium',
|
||||||
|
variant: 'solid',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface BadgeProps
|
||||||
|
extends HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof badgeVariants> {}
|
||||||
|
|
||||||
|
const Badge = ({ className, theme, size, variant, ...props }: BadgeProps) => {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
className={twMerge(badgeVariants({ theme, size, variant, className }))}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Badge }
|
||||||
131
joi/src/core/Badge/styles.scss
Normal file
131
joi/src/core/Badge/styles.scss
Normal file
@ -0,0 +1,131 @@
|
|||||||
|
.badge {
|
||||||
|
@apply inline-flex items-center justify-center px-2 font-medium transition-all;
|
||||||
|
|
||||||
|
// Primary
|
||||||
|
&--primary {
|
||||||
|
color: hsla(var(--primary-fg));
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
|
||||||
|
// Variant soft primary
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--primary-bg-soft));
|
||||||
|
color: hsla(var(--primary-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline primary
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--primary-bg));
|
||||||
|
color: hsla(var(--primary-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Secondary
|
||||||
|
&--secondary {
|
||||||
|
background-color: hsla(var(--secondary-bg));
|
||||||
|
color: hsla(var(--secondary-fg));
|
||||||
|
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--secondary-bg-soft));
|
||||||
|
color: hsla(var(--secondary-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline secondary
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--secondary-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructive
|
||||||
|
&--destructive {
|
||||||
|
color: hsla(var(--destructive-fg));
|
||||||
|
background-color: hsla(var(--destructive-bg));
|
||||||
|
|
||||||
|
// Variant soft destructive
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--destructive-bg-soft));
|
||||||
|
color: hsla(var(--destructive-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline destructive
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--destructive-bg));
|
||||||
|
color: hsla(var(--destructive-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Success
|
||||||
|
&--success {
|
||||||
|
@apply text-white;
|
||||||
|
background-color: hsla(var(--success-bg));
|
||||||
|
|
||||||
|
// Variant soft success
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--success-bg-soft));
|
||||||
|
color: hsla(var(--success-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline success
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--success-bg));
|
||||||
|
color: hsla(var(--success-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Warning
|
||||||
|
&--warning {
|
||||||
|
@apply text-white;
|
||||||
|
background-color: hsla(var(--warning-bg));
|
||||||
|
|
||||||
|
// Variant soft warning
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--warning-bg-soft));
|
||||||
|
color: hsla(var(--warning-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline warning
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--warning-bg));
|
||||||
|
color: hsla(var(--warning-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Info
|
||||||
|
&--info {
|
||||||
|
@apply text-white;
|
||||||
|
background-color: hsla(var(--info-bg));
|
||||||
|
|
||||||
|
// Variant soft info
|
||||||
|
&.badge--soft {
|
||||||
|
background-color: hsla(var(--info-bg-soft));
|
||||||
|
color: hsla(var(--info-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline info
|
||||||
|
&.badge--outline {
|
||||||
|
background-color: transparent;
|
||||||
|
border: 1px solid hsla(var(--info-bg));
|
||||||
|
color: hsla(var(--info-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size
|
||||||
|
&--small {
|
||||||
|
@apply h-5;
|
||||||
|
border-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
@apply h-6;
|
||||||
|
border-radius: 6px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--large {
|
||||||
|
@apply h-7;
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
64
joi/src/core/Button/index.tsx
Normal file
64
joi/src/core/Button/index.tsx
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
import React, { forwardRef, ButtonHTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
import { Slot } from '@radix-ui/react-slot'
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
const buttonVariants = cva('btn', {
|
||||||
|
variants: {
|
||||||
|
theme: {
|
||||||
|
primary: 'btn--primary',
|
||||||
|
ghost: 'btn--ghost',
|
||||||
|
icon: 'btn--icon',
|
||||||
|
destructive: 'btn--destructive',
|
||||||
|
},
|
||||||
|
variant: {
|
||||||
|
solid: 'btn--solid',
|
||||||
|
soft: 'btn--soft',
|
||||||
|
outline: 'btn--outline',
|
||||||
|
},
|
||||||
|
size: {
|
||||||
|
small: 'btn--small',
|
||||||
|
medium: 'btn--medium',
|
||||||
|
large: 'btn--large',
|
||||||
|
},
|
||||||
|
block: {
|
||||||
|
true: 'btn--block',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
theme: 'primary',
|
||||||
|
size: 'medium',
|
||||||
|
variant: 'solid',
|
||||||
|
block: false,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ButtonProps
|
||||||
|
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
||||||
|
VariantProps<typeof buttonVariants> {
|
||||||
|
asChild?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
||||||
|
(
|
||||||
|
{ className, theme, size, variant, block, asChild = false, ...props },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
const Comp = asChild ? Slot : 'button'
|
||||||
|
return (
|
||||||
|
<Comp
|
||||||
|
className={twMerge(
|
||||||
|
buttonVariants({ theme, size, variant, block, className })
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Button }
|
||||||
134
joi/src/core/Button/styles.scss
Normal file
134
joi/src/core/Button/styles.scss
Normal file
@ -0,0 +1,134 @@
|
|||||||
|
.btn {
|
||||||
|
@apply inline-flex items-center justify-center px-4 font-semibold transition-all;
|
||||||
|
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
@apply outline-2 outline-offset-4;
|
||||||
|
}
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(95%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Primary
|
||||||
|
&--primary {
|
||||||
|
color: hsla(var(--primary-fg));
|
||||||
|
background-color: hsla(var(--primary-bg)) !important;
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant soft primary
|
||||||
|
&.btn--soft {
|
||||||
|
background-color: hsla(var(--primary-bg-soft)) !important;
|
||||||
|
color: hsla(var(--primary-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline primary
|
||||||
|
&.btn--outline {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid hsla(var(--primary-bg));
|
||||||
|
color: hsla(var(--primary-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Ghost
|
||||||
|
&--ghost {
|
||||||
|
background-color: transparent !important;
|
||||||
|
&.btn--soft {
|
||||||
|
background-color: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline ghost
|
||||||
|
&.btn--outline {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid hsla(var(--ghost-border));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Destructive
|
||||||
|
&--destructive {
|
||||||
|
color: hsla(var(--destructive-fg));
|
||||||
|
background-color: hsla(var(--destructive-bg)) !important;
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(65%);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant soft destructive
|
||||||
|
&.btn--soft {
|
||||||
|
background-color: hsla(var(--destructive-bg-soft)) !important;
|
||||||
|
color: hsla(var(--destructive-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
// Variant outline destructive
|
||||||
|
&.btn--outline {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid hsla(var(--destructive-bg));
|
||||||
|
color: hsla(var(--destructive-bg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Disabled
|
||||||
|
&:disabled {
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
background-color: hsla(var(--disabled-bg)) !important;
|
||||||
|
cursor: not-allowed;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
filter: brightness(100%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Icon
|
||||||
|
&--icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
&:hover {
|
||||||
|
background-color: hsla(var(--icon-bg)) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.btn--outline {
|
||||||
|
background-color: transparent !important;
|
||||||
|
border: 1px solid hsla(var(--icon-border));
|
||||||
|
&:hover {
|
||||||
|
background-color: hsla(var(--icon-bg)) !important;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Size
|
||||||
|
&--small {
|
||||||
|
@apply h-6 px-2;
|
||||||
|
font-size: 12px;
|
||||||
|
border-radius: 4px;
|
||||||
|
&.btn--icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--medium {
|
||||||
|
@apply h-8;
|
||||||
|
border-radius: 6px;
|
||||||
|
&.btn--icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--large {
|
||||||
|
@apply h-9;
|
||||||
|
border-radius: 8px;
|
||||||
|
&.btn--icon {
|
||||||
|
width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
padding: 2px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--block {
|
||||||
|
@apply w-full;
|
||||||
|
}
|
||||||
|
}
|
||||||
51
joi/src/core/Checkbox/index.tsx
Normal file
51
joi/src/core/Checkbox/index.tsx
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import React, { ChangeEvent, InputHTMLAttributes, ReactNode } from 'react'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
export interface CheckboxProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
label?: ReactNode
|
||||||
|
helperDescription?: ReactNode
|
||||||
|
errorMessage?: string
|
||||||
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Checkbox = ({
|
||||||
|
id,
|
||||||
|
name,
|
||||||
|
checked,
|
||||||
|
disabled,
|
||||||
|
label,
|
||||||
|
defaultChecked,
|
||||||
|
helperDescription,
|
||||||
|
errorMessage,
|
||||||
|
className,
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}: CheckboxProps) => {
|
||||||
|
return (
|
||||||
|
<div className={twMerge('checkbox', className)}>
|
||||||
|
<input
|
||||||
|
id={id}
|
||||||
|
type="checkbox"
|
||||||
|
name={name}
|
||||||
|
defaultChecked={defaultChecked}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={onChange}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<div>
|
||||||
|
<label htmlFor={id} className="checkbox__label">
|
||||||
|
{label}
|
||||||
|
</label>
|
||||||
|
<p className="checkbox__helper">{helperDescription}</p>
|
||||||
|
{errorMessage && <p className="checkbox__error">{errorMessage}</p>}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export { Checkbox }
|
||||||
51
joi/src/core/Checkbox/styles.scss
Normal file
51
joi/src/core/Checkbox/styles.scss
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
.checkbox {
|
||||||
|
@apply inline-flex items-start space-x-2;
|
||||||
|
|
||||||
|
> input[type='checkbox'] {
|
||||||
|
@apply flex h-4 w-4 flex-shrink-0 cursor-pointer appearance-none items-center justify-center;
|
||||||
|
background-color: transparent;
|
||||||
|
margin-top: 1px;
|
||||||
|
border: 1px solid hsla(var(--app-border));
|
||||||
|
border-radius: 4px;
|
||||||
|
&:focus,
|
||||||
|
&:focus-within {
|
||||||
|
@apply outline-2 outline-offset-4;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
background-image: url("data:image/svg+xml;charset=utf-8,%3Csvg viewBox='0 0 16 16' fill='%23fff' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath d='M12.207 4.793a1 1 0 0 1 0 1.414l-5 5a1 1 0 0 1-1.414 0l-2-2a1 1 0 0 1 1.414-1.414L6.5 9.086l4.293-4.293a1 1 0 0 1 1.414 0z'/%3E%3C/svg%3E");
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: hsla(var(----disabled-bg));
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
|
||||||
|
&:checked {
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
@apply cursor-not-allowed opacity-50;
|
||||||
|
}
|
||||||
|
|
||||||
|
& + div > .checkbox__label {
|
||||||
|
@apply cursor-not-allowed opacity-50;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__helper {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__error {
|
||||||
|
color: hsla(var(--destructive-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
@apply inline-block cursor-pointer;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
background-color: hsla(var(----disabled-bg));
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
46
joi/src/core/Input/index.tsx
Normal file
46
joi/src/core/Input/index.tsx
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import React, { ReactNode, forwardRef } from 'react'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
export interface Props extends React.InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
textAlign?: 'left' | 'right'
|
||||||
|
prefixIcon?: ReactNode
|
||||||
|
suffixIcon?: ReactNode
|
||||||
|
onCLick?: () => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Input = forwardRef<HTMLInputElement, Props>(
|
||||||
|
(
|
||||||
|
{ className, type, textAlign, prefixIcon, suffixIcon, onClick, ...props },
|
||||||
|
ref
|
||||||
|
) => {
|
||||||
|
return (
|
||||||
|
<div className="input__wrapper">
|
||||||
|
{prefixIcon && (
|
||||||
|
<div className="input__prefix-icon" onClick={onClick}>
|
||||||
|
{prefixIcon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
{suffixIcon && (
|
||||||
|
<div className="input__suffix-icon" onClick={onClick}>
|
||||||
|
{suffixIcon}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<input
|
||||||
|
type={type}
|
||||||
|
className={twMerge(
|
||||||
|
'input',
|
||||||
|
className,
|
||||||
|
textAlign === 'right' && 'text-right'
|
||||||
|
)}
|
||||||
|
ref={ref}
|
||||||
|
onClick={onClick}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Input }
|
||||||
43
joi/src/core/Input/styles.scss
Normal file
43
joi/src/core/Input/styles.scss
Normal file
@ -0,0 +1,43 @@
|
|||||||
|
.input {
|
||||||
|
background-color: hsla(var(--input-bg));
|
||||||
|
border: 1px solid hsla(var(--app-border));
|
||||||
|
@apply inline-flex h-8 w-full items-center rounded-md border px-3 transition-colors;
|
||||||
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0;
|
||||||
|
@apply file:border-0 file:bg-transparent file:font-medium;
|
||||||
|
@apply hover:border-[hsla(var(--primary-bg))];
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: hsla(var(--input-placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
background-color: hsla(var(--disabled-bg));
|
||||||
|
cursor: not-allowed;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__prefix-icon {
|
||||||
|
@apply absolute left-3 top-1/2 -translate-y-1/2 cursor-pointer;
|
||||||
|
color: hsla(var(--input-icon));
|
||||||
|
+ .input {
|
||||||
|
padding-left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__suffix-icon {
|
||||||
|
@apply absolute right-3 top-1/2 -translate-y-1/2 cursor-pointer;
|
||||||
|
color: hsla(var(--input-icon));
|
||||||
|
+ .input {
|
||||||
|
padding-right: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
56
joi/src/core/Modal/index.tsx
Normal file
56
joi/src/core/Modal/index.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
import * as DialogPrimitive from '@radix-ui/react-dialog'
|
||||||
|
import { Cross2Icon } from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
trigger?: ReactNode
|
||||||
|
content: ReactNode
|
||||||
|
open?: boolean
|
||||||
|
className?: string
|
||||||
|
fullPage?: boolean
|
||||||
|
hideClose?: boolean
|
||||||
|
title?: ReactNode
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModalClose = DialogPrimitive.Close
|
||||||
|
|
||||||
|
const Modal = ({
|
||||||
|
trigger,
|
||||||
|
content,
|
||||||
|
open,
|
||||||
|
title,
|
||||||
|
fullPage,
|
||||||
|
className,
|
||||||
|
onOpenChange,
|
||||||
|
hideClose,
|
||||||
|
}: Props) => (
|
||||||
|
<DialogPrimitive.Root open={open} onOpenChange={onOpenChange}>
|
||||||
|
<DialogPrimitive.Trigger asChild>{trigger}</DialogPrimitive.Trigger>
|
||||||
|
<DialogPrimitive.Portal>
|
||||||
|
<DialogPrimitive.Overlay className="modal__overlay" />
|
||||||
|
<DialogPrimitive.Content
|
||||||
|
className={twMerge(
|
||||||
|
'modal__content',
|
||||||
|
fullPage && 'modal__content--fullpage',
|
||||||
|
className
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<div className="modal__title">{title}</div>
|
||||||
|
{content}
|
||||||
|
{!hideClose && (
|
||||||
|
<ModalClose asChild>
|
||||||
|
<button className="modal__close-icon" aria-label="Close">
|
||||||
|
<Cross2Icon />
|
||||||
|
</button>
|
||||||
|
</ModalClose>
|
||||||
|
)}
|
||||||
|
</DialogPrimitive.Content>
|
||||||
|
</DialogPrimitive.Portal>
|
||||||
|
</DialogPrimitive.Root>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Modal, ModalClose }
|
||||||
86
joi/src/core/Modal/styles.scss
Normal file
86
joi/src/core/Modal/styles.scss
Normal file
@ -0,0 +1,86 @@
|
|||||||
|
/* reset */
|
||||||
|
button,
|
||||||
|
fieldset,
|
||||||
|
.modal {
|
||||||
|
&__overlay {
|
||||||
|
@apply backdrop-blur-lg;
|
||||||
|
background-color: hsla(var(--modal-overlay));
|
||||||
|
z-index: 200;
|
||||||
|
position: fixed;
|
||||||
|
inset: 0;
|
||||||
|
animation: overlayShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
color: hsla(var(--modal-fg));
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: hsla(var(--modal-bg));
|
||||||
|
border-radius: 8px;
|
||||||
|
font-size: 14px;
|
||||||
|
position: fixed;
|
||||||
|
z-index: 300;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 50vw;
|
||||||
|
max-width: 560px;
|
||||||
|
max-height: 85vh;
|
||||||
|
padding: 16px;
|
||||||
|
animation: contentShow 150ms cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
border: 1px solid hsla(var(--app-border));
|
||||||
|
@apply w-full;
|
||||||
|
|
||||||
|
&--fullpage {
|
||||||
|
max-width: none;
|
||||||
|
width: 90vw;
|
||||||
|
max-height: 90vh;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
@apply line-clamp-1;
|
||||||
|
margin: 0 0 8px 0;
|
||||||
|
padding-right: 16px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: hsla(var(--modal-fg));
|
||||||
|
font-size: 18px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__close-icon {
|
||||||
|
font-family: inherit;
|
||||||
|
border-radius: 100%;
|
||||||
|
height: 24px;
|
||||||
|
width: 24px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
color: hsla(var(--modal-fg));
|
||||||
|
position: absolute;
|
||||||
|
top: 8px;
|
||||||
|
right: 16px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes overlayShow {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes contentShow {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translate(-50%, -48%) scale(0.96);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translate(-50%, -50%) scale(1);
|
||||||
|
}
|
||||||
|
}
|
||||||
39
joi/src/core/Progress/index.tsx
Normal file
39
joi/src/core/Progress/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { HTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
import { cva, type VariantProps } from 'class-variance-authority'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
const progressVariants = cva('progress', {
|
||||||
|
variants: {
|
||||||
|
size: {
|
||||||
|
small: 'progress--small',
|
||||||
|
medium: 'progress--medium',
|
||||||
|
large: 'progress--large',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
defaultVariants: {
|
||||||
|
size: 'medium',
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
||||||
|
export interface ProgressProps
|
||||||
|
extends HTMLAttributes<HTMLDivElement>,
|
||||||
|
VariantProps<typeof progressVariants> {
|
||||||
|
value: number
|
||||||
|
}
|
||||||
|
|
||||||
|
const Progress = ({ className, size, value, ...props }: ProgressProps) => {
|
||||||
|
return (
|
||||||
|
<div className={twMerge(progressVariants({ size, className }))} {...props}>
|
||||||
|
<div
|
||||||
|
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
||||||
|
className="progress--indicator"
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export { Progress }
|
||||||
25
joi/src/core/Progress/styles.scss
Normal file
25
joi/src/core/Progress/styles.scss
Normal file
@ -0,0 +1,25 @@
|
|||||||
|
.progress {
|
||||||
|
background-color: hsla(var(--progress-track-bg));
|
||||||
|
border-radius: 8px;
|
||||||
|
position: relative;
|
||||||
|
overflow: hidden;
|
||||||
|
@apply transition-all;
|
||||||
|
|
||||||
|
&--indicator {
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
position: absolute;
|
||||||
|
border-radius: 8px;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&--small {
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
&--medium {
|
||||||
|
@apply h-2;
|
||||||
|
}
|
||||||
|
&--large {
|
||||||
|
@apply h-3;
|
||||||
|
}
|
||||||
|
}
|
||||||
39
joi/src/core/ScrollArea/index.tsx
Normal file
39
joi/src/core/ScrollArea/index.tsx
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
import React, { PropsWithChildren, forwardRef } from 'react'
|
||||||
|
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
const ScrollArea = React.forwardRef<
|
||||||
|
React.ElementRef<typeof ScrollAreaPrimitive.Root>,
|
||||||
|
React.ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
||||||
|
>(({ className, children, onScroll, ...props }, ref) => (
|
||||||
|
<ScrollAreaPrimitive.Root
|
||||||
|
type="scroll"
|
||||||
|
className={twMerge('scroll-area__root', className)}
|
||||||
|
{...props}
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Viewport
|
||||||
|
className="scroll-area__viewport"
|
||||||
|
ref={ref}
|
||||||
|
onScroll={onScroll}
|
||||||
|
>
|
||||||
|
{children}
|
||||||
|
</ScrollAreaPrimitive.Viewport>
|
||||||
|
<ScrollAreaPrimitive.Scrollbar
|
||||||
|
className="scroll-area__bar"
|
||||||
|
orientation="horizontal"
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Thumb />
|
||||||
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
|
<ScrollAreaPrimitive.Scrollbar
|
||||||
|
className="scroll-area__bar"
|
||||||
|
orientation="vertical"
|
||||||
|
>
|
||||||
|
<ScrollAreaPrimitive.Thumb className="scroll-area__thumb" />
|
||||||
|
</ScrollAreaPrimitive.Scrollbar>
|
||||||
|
<ScrollAreaPrimitive.Corner className="scroll-area__corner" />
|
||||||
|
</ScrollAreaPrimitive.Root>
|
||||||
|
))
|
||||||
|
|
||||||
|
export { ScrollArea }
|
||||||
69
joi/src/core/ScrollArea/styles.scss
Normal file
69
joi/src/core/ScrollArea/styles.scss
Normal file
@ -0,0 +1,69 @@
|
|||||||
|
.scroll-area {
|
||||||
|
position: relative;
|
||||||
|
z-index: 999;
|
||||||
|
|
||||||
|
&__root {
|
||||||
|
width: 200px;
|
||||||
|
height: 225px;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__viewport {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bar {
|
||||||
|
display: flex;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: none;
|
||||||
|
padding: 1px;
|
||||||
|
background: hsla(var(--scrollbar-tracker));
|
||||||
|
transition: background 160ms ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__thumb {
|
||||||
|
flex: 1;
|
||||||
|
background: hsla(var(--scrollbar-thumb));
|
||||||
|
border-radius: 20px;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
::before {
|
||||||
|
content: '';
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
min-width: 44px;
|
||||||
|
min-height: 44px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-area__bar[data-orientation='vertical'] {
|
||||||
|
width: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.scroll-area__bar[data-orientation='horizontal'] {
|
||||||
|
flex-direction: column;
|
||||||
|
height: 8px;
|
||||||
|
}
|
||||||
|
|
||||||
|
::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track,
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background-clip: content-box;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-track {
|
||||||
|
background: hsla(var(--scrollbar-tracker));
|
||||||
|
}
|
||||||
|
::-webkit-scrollbar-thumb {
|
||||||
|
background: hsla(var(--scrollbar-thumb));
|
||||||
|
}
|
||||||
85
joi/src/core/Select/index.tsx
Normal file
85
joi/src/core/Select/index.tsx
Normal file
@ -0,0 +1,85 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import * as SelectPrimitive from '@radix-ui/react-select'
|
||||||
|
import {
|
||||||
|
CheckIcon,
|
||||||
|
ChevronDownIcon,
|
||||||
|
ChevronUpIcon,
|
||||||
|
} from '@radix-ui/react-icons'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
options?: { name: string; value: string }[]
|
||||||
|
open?: boolean
|
||||||
|
block?: boolean
|
||||||
|
value?: string
|
||||||
|
placeholder?: string
|
||||||
|
disabled?: boolean
|
||||||
|
containerPortal?: HTMLDivElement | undefined | null
|
||||||
|
className?: string
|
||||||
|
onValueChange?: (value: string) => void
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Select = ({
|
||||||
|
placeholder,
|
||||||
|
options,
|
||||||
|
value,
|
||||||
|
disabled,
|
||||||
|
containerPortal,
|
||||||
|
block,
|
||||||
|
className,
|
||||||
|
open,
|
||||||
|
onValueChange,
|
||||||
|
onOpenChange,
|
||||||
|
}: Props) => (
|
||||||
|
<SelectPrimitive.Root
|
||||||
|
open={open}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
value={value}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<SelectPrimitive.Trigger
|
||||||
|
className={twMerge(
|
||||||
|
'select',
|
||||||
|
className,
|
||||||
|
disabled && 'select__disabled',
|
||||||
|
block && 'w-full'
|
||||||
|
)}
|
||||||
|
>
|
||||||
|
<SelectPrimitive.Value placeholder={placeholder} />
|
||||||
|
<SelectPrimitive.Icon className="select__icon">
|
||||||
|
<ChevronDownIcon />
|
||||||
|
</SelectPrimitive.Icon>
|
||||||
|
</SelectPrimitive.Trigger>
|
||||||
|
|
||||||
|
<SelectPrimitive.Portal container={containerPortal}>
|
||||||
|
<SelectPrimitive.Content className="select__content">
|
||||||
|
<SelectPrimitive.Viewport className="select__viewport">
|
||||||
|
{options &&
|
||||||
|
options.map((item, i) => {
|
||||||
|
return (
|
||||||
|
<SelectPrimitive.Item
|
||||||
|
key={i}
|
||||||
|
className="select__item"
|
||||||
|
value={item.value}
|
||||||
|
>
|
||||||
|
<SelectPrimitive.ItemText>
|
||||||
|
{item.name}
|
||||||
|
</SelectPrimitive.ItemText>
|
||||||
|
<SelectPrimitive.ItemIndicator className="select__item-indicator">
|
||||||
|
<CheckIcon />
|
||||||
|
</SelectPrimitive.ItemIndicator>
|
||||||
|
</SelectPrimitive.Item>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</SelectPrimitive.Viewport>
|
||||||
|
<SelectPrimitive.Arrow />
|
||||||
|
</SelectPrimitive.Content>
|
||||||
|
</SelectPrimitive.Portal>
|
||||||
|
</SelectPrimitive.Root>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Select }
|
||||||
77
joi/src/core/Select/styles.scss
Normal file
77
joi/src/core/Select/styles.scss
Normal file
@ -0,0 +1,77 @@
|
|||||||
|
.select {
|
||||||
|
padding: 0 16px;
|
||||||
|
background-color: hsla(var(--select-input-bg)) !important;
|
||||||
|
border: 1px solid hsla(var(--app-border));
|
||||||
|
@apply inline-flex h-8 items-center justify-between gap-8 rounded-md px-3 transition-colors;
|
||||||
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0;
|
||||||
|
@apply text-sm hover:border-[hsla(var(--primary-bg))];
|
||||||
|
|
||||||
|
&[data-placeholder] {
|
||||||
|
color: hsla(var(--select-placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__icon {
|
||||||
|
color: hsla(var(--select-icon));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
overflow: hidden;
|
||||||
|
background-color: hsla(var(--select-bg));
|
||||||
|
z-index: 999;
|
||||||
|
border: 1px solid hsla(var(--select-border));
|
||||||
|
border-radius: 8px;
|
||||||
|
box-shadow:
|
||||||
|
0px 10px 38px -10px rgba(22, 23, 24, 0.35),
|
||||||
|
0px 10px 20px -15px rgba(22, 23, 24, 0.2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__viewport {
|
||||||
|
}
|
||||||
|
|
||||||
|
&__disabled {
|
||||||
|
cursor: not-allowed;
|
||||||
|
pointer-events: none;
|
||||||
|
background-color: hsla(var(--disabled-bg)) !important;
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding: 8px 32px 8px 16px;
|
||||||
|
position: relative;
|
||||||
|
cursor: pointer;
|
||||||
|
@apply text-sm;
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: hsla(var(--select-options-active-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-disabled] {
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&[data-highlighted] {
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__item-indicator {
|
||||||
|
position: absolute;
|
||||||
|
right: 0;
|
||||||
|
width: 25px;
|
||||||
|
display: inline-flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__scroll-button {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
height: 25px;
|
||||||
|
background-color: white;
|
||||||
|
cursor: default;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
joi/src/core/Slider/index.tsx
Normal file
45
joi/src/core/Slider/index.tsx
Normal file
@ -0,0 +1,45 @@
|
|||||||
|
import React from 'react'
|
||||||
|
import * as SliderPrimitive from '@radix-ui/react-slider'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
name?: string
|
||||||
|
min?: number
|
||||||
|
max?: number
|
||||||
|
onValueChange?(value: number[]): void
|
||||||
|
value?: number[]
|
||||||
|
defaultValue?: number[]
|
||||||
|
step?: number
|
||||||
|
disabled?: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
const Slider = ({
|
||||||
|
name,
|
||||||
|
min,
|
||||||
|
max,
|
||||||
|
onValueChange,
|
||||||
|
value,
|
||||||
|
defaultValue,
|
||||||
|
step,
|
||||||
|
disabled,
|
||||||
|
}: Props) => (
|
||||||
|
<SliderPrimitive.Root
|
||||||
|
className="slider"
|
||||||
|
name={name}
|
||||||
|
min={min}
|
||||||
|
max={max}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
value={value}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
step={step}
|
||||||
|
disabled={disabled}
|
||||||
|
>
|
||||||
|
<SliderPrimitive.Track className="slider__track">
|
||||||
|
<SliderPrimitive.Range className="slider__range" />
|
||||||
|
</SliderPrimitive.Track>
|
||||||
|
<SliderPrimitive.Thumb className="slider__thumb" />
|
||||||
|
</SliderPrimitive.Root>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Slider }
|
||||||
38
joi/src/core/Slider/styles.scss
Normal file
38
joi/src/core/Slider/styles.scss
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
.slider {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
user-select: none;
|
||||||
|
touch-action: none;
|
||||||
|
height: 16px;
|
||||||
|
|
||||||
|
&__track {
|
||||||
|
background-color: hsla(var(--slider-track-bg));
|
||||||
|
position: relative;
|
||||||
|
flex-grow: 1;
|
||||||
|
border-radius: 9999px;
|
||||||
|
height: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__range {
|
||||||
|
position: absolute;
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
border-radius: 9999px;
|
||||||
|
height: 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__thumb {
|
||||||
|
display: block;
|
||||||
|
width: 16px;
|
||||||
|
height: 16px;
|
||||||
|
background-color: hsla(var(--slider-thumb-bg));
|
||||||
|
border-radius: 10px;
|
||||||
|
padding: 2px;
|
||||||
|
border: 2px solid hsla(var(--primary-bg));
|
||||||
|
|
||||||
|
&:focus {
|
||||||
|
outline: none;
|
||||||
|
box-shadow: 0 0 0 5px hsla(var(--slider-track-bg), 50%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
37
joi/src/core/Switch/index.tsx
Normal file
37
joi/src/core/Switch/index.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React, { ChangeEvent, InputHTMLAttributes } from 'react'
|
||||||
|
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
export interface SwitchProps extends InputHTMLAttributes<HTMLInputElement> {
|
||||||
|
disabled?: boolean
|
||||||
|
className?: string
|
||||||
|
onChange?: (e: ChangeEvent<HTMLInputElement>) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
const Switch = ({
|
||||||
|
name,
|
||||||
|
checked,
|
||||||
|
disabled,
|
||||||
|
defaultChecked,
|
||||||
|
className,
|
||||||
|
onChange,
|
||||||
|
...props
|
||||||
|
}: SwitchProps) => {
|
||||||
|
return (
|
||||||
|
<label className={twMerge('switch', className)}>
|
||||||
|
<input
|
||||||
|
type="checkbox"
|
||||||
|
name={name}
|
||||||
|
defaultChecked={defaultChecked}
|
||||||
|
checked={checked}
|
||||||
|
disabled={disabled}
|
||||||
|
onChange={onChange}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
<span className="switch--thumb" />
|
||||||
|
</label>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export { Switch }
|
||||||
67
joi/src/core/Switch/styles.scss
Normal file
67
joi/src/core/Switch/styles.scss
Normal file
@ -0,0 +1,67 @@
|
|||||||
|
.switch {
|
||||||
|
position: relative;
|
||||||
|
display: inline-block;
|
||||||
|
width: 32px;
|
||||||
|
height: 18px;
|
||||||
|
|
||||||
|
> input {
|
||||||
|
opacity: 0;
|
||||||
|
width: 0;
|
||||||
|
height: 0;
|
||||||
|
|
||||||
|
// disabled
|
||||||
|
&:disabled {
|
||||||
|
+ .switch--thumb {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: hsla(var(--disabled-bg));
|
||||||
|
&:before {
|
||||||
|
background-color: hsla(var(--disabled-fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// disabled and checked
|
||||||
|
&:checked + .switch--thumb {
|
||||||
|
cursor: not-allowed;
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
&:before {
|
||||||
|
background-color: hsla(var(--disabled-fg));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&:checked + .switch--thumb {
|
||||||
|
background-color: hsla(var(--primary-bg));
|
||||||
|
|
||||||
|
&::before {
|
||||||
|
-webkit-transform: translateX(14px);
|
||||||
|
-ms-transform: translateX(14px);
|
||||||
|
transform: translateX(14px);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--thumb {
|
||||||
|
position: absolute;
|
||||||
|
cursor: pointer;
|
||||||
|
top: 0;
|
||||||
|
left: 0;
|
||||||
|
right: 0;
|
||||||
|
bottom: 0;
|
||||||
|
background-color: hsla(var(--switch-bg));
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 20px;
|
||||||
|
|
||||||
|
&:before {
|
||||||
|
position: absolute;
|
||||||
|
content: '';
|
||||||
|
height: 14px;
|
||||||
|
width: 14px;
|
||||||
|
left: 2px;
|
||||||
|
bottom: 2px;
|
||||||
|
background-color: hsla(var(--switch-fg));
|
||||||
|
-webkit-transition: 0.4s;
|
||||||
|
transition: 0.4s;
|
||||||
|
border-radius: 50%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
59
joi/src/core/Tabs/index.tsx
Normal file
59
joi/src/core/Tabs/index.tsx
Normal file
@ -0,0 +1,59 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
|
||||||
|
import * as TabsPrimitive from '@radix-ui/react-tabs'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
type TabsProps = {
|
||||||
|
options: { name: string; value: string }[]
|
||||||
|
children: ReactNode
|
||||||
|
defaultValue?: string
|
||||||
|
value: string
|
||||||
|
onValueChange?: (value: string) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
type TabsContentProps = {
|
||||||
|
value: string
|
||||||
|
children: ReactNode
|
||||||
|
}
|
||||||
|
|
||||||
|
const TabsContent = ({ value, children }: TabsContentProps) => {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Content className="tabs__content" value={value}>
|
||||||
|
{children}
|
||||||
|
</TabsPrimitive.Content>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const Tabs = ({
|
||||||
|
options,
|
||||||
|
children,
|
||||||
|
defaultValue,
|
||||||
|
value,
|
||||||
|
onValueChange,
|
||||||
|
}: TabsProps) => (
|
||||||
|
<TabsPrimitive.Root
|
||||||
|
className="tabs"
|
||||||
|
value={value}
|
||||||
|
defaultValue={defaultValue}
|
||||||
|
onValueChange={onValueChange}
|
||||||
|
>
|
||||||
|
<TabsPrimitive.List className="tabs__list">
|
||||||
|
{options.map((option, i) => {
|
||||||
|
return (
|
||||||
|
<TabsPrimitive.Trigger
|
||||||
|
key={i}
|
||||||
|
className="tabs__trigger"
|
||||||
|
value={option.value}
|
||||||
|
>
|
||||||
|
{option.name}
|
||||||
|
</TabsPrimitive.Trigger>
|
||||||
|
)
|
||||||
|
})}
|
||||||
|
</TabsPrimitive.List>
|
||||||
|
|
||||||
|
{children}
|
||||||
|
</TabsPrimitive.Root>
|
||||||
|
)
|
||||||
|
|
||||||
|
export { Tabs, TabsContent }
|
||||||
37
joi/src/core/Tabs/styles.scss
Normal file
37
joi/src/core/Tabs/styles.scss
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
.tabs {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
width: 100%;
|
||||||
|
|
||||||
|
&__list {
|
||||||
|
flex-shrink: 0;
|
||||||
|
display: flex;
|
||||||
|
border-bottom: 1px solid hsla(var(--app-border));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__trigger {
|
||||||
|
padding: 0 12px;
|
||||||
|
flex: 1;
|
||||||
|
height: 38px;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
line-height: 1;
|
||||||
|
user-select: none;
|
||||||
|
&:focus {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
flex-grow: 1;
|
||||||
|
border-bottom-left-radius: 6px;
|
||||||
|
border-bottom-right-radius: 6px;
|
||||||
|
outline: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tabs__trigger[data-state='active'] {
|
||||||
|
border-bottom: 1px solid hsla(var(--primary-bg));
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
24
joi/src/core/TextArea/index.tsx
Normal file
24
joi/src/core/TextArea/index.tsx
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import React, { ReactNode, forwardRef } from 'react'
|
||||||
|
import { twMerge } from 'tailwind-merge'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
import { ScrollArea } from '../ScrollArea'
|
||||||
|
|
||||||
|
export interface TextAreaProps
|
||||||
|
extends React.TextareaHTMLAttributes<HTMLTextAreaElement> {}
|
||||||
|
|
||||||
|
const TextArea = forwardRef<HTMLTextAreaElement, TextAreaProps>(
|
||||||
|
({ className, ...props }, ref) => {
|
||||||
|
return (
|
||||||
|
<div className="textarea__wrapper">
|
||||||
|
<textarea
|
||||||
|
className={twMerge('textarea', className)}
|
||||||
|
ref={ref}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
</div>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
export { TextArea }
|
||||||
54
joi/src/core/TextArea/styles.scss
Normal file
54
joi/src/core/TextArea/styles.scss
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
.textarea {
|
||||||
|
background-color: hsla(var(--textarea-bg));
|
||||||
|
border: 1px solid hsla(var(--app-border));
|
||||||
|
@apply inline-flex w-full items-center rounded-md border p-3 transition-colors;
|
||||||
|
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-1 focus-visible:ring-[hsla(var(--primary-bg))] focus-visible:ring-offset-0;
|
||||||
|
@apply file:border-0 file:bg-transparent file:font-medium;
|
||||||
|
@apply hover:border-[hsla(var(--primary-bg))];
|
||||||
|
|
||||||
|
&__wrapper {
|
||||||
|
position: relative;
|
||||||
|
}
|
||||||
|
|
||||||
|
&.text-right {
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
|
||||||
|
&::placeholder {
|
||||||
|
color: hsla(var(--textarea-placeholder));
|
||||||
|
}
|
||||||
|
|
||||||
|
&:disabled {
|
||||||
|
color: hsla(var(--disabled-fg));
|
||||||
|
background-color: hsla(var(--disabled-bg));
|
||||||
|
cursor: not-allowed;
|
||||||
|
border: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__prefix-icon {
|
||||||
|
@apply absolute left-2 top-1/2 -translate-y-1/2;
|
||||||
|
color: hsla(var(--textarea-icon));
|
||||||
|
+ .textarea {
|
||||||
|
padding-left: 32px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
& {
|
||||||
|
/* Arrow mouse cursor over the scrollbar */
|
||||||
|
cursor: auto;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar {
|
||||||
|
width: 6px;
|
||||||
|
height: 6px;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track,
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background-clip: content-box;
|
||||||
|
border-radius: inherit;
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-track {
|
||||||
|
background: hsla(var(--scrollbar-tracker));
|
||||||
|
}
|
||||||
|
&::-webkit-scrollbar-thumb {
|
||||||
|
background: hsla(var(--scrollbar-thumb));
|
||||||
|
}
|
||||||
|
}
|
||||||
53
joi/src/core/Tooltip/index.tsx
Normal file
53
joi/src/core/Tooltip/index.tsx
Normal file
@ -0,0 +1,53 @@
|
|||||||
|
import React, { ReactNode } from 'react'
|
||||||
|
import * as TooltipPrimitive from '@radix-ui/react-tooltip'
|
||||||
|
|
||||||
|
import './styles.scss'
|
||||||
|
|
||||||
|
export interface TooltipProps {
|
||||||
|
trigger?: ReactNode
|
||||||
|
content: ReactNode
|
||||||
|
side?: 'top' | 'right' | 'bottom' | 'left'
|
||||||
|
open?: boolean
|
||||||
|
disabled?: boolean
|
||||||
|
withArrow?: boolean
|
||||||
|
onOpenChange?: (open: boolean) => void
|
||||||
|
}
|
||||||
|
|
||||||
|
export const Tooltip = ({
|
||||||
|
trigger,
|
||||||
|
disabled,
|
||||||
|
content,
|
||||||
|
side = 'top',
|
||||||
|
withArrow = true,
|
||||||
|
open,
|
||||||
|
onOpenChange,
|
||||||
|
}: TooltipProps) => {
|
||||||
|
return (
|
||||||
|
<TooltipPrimitive.Provider>
|
||||||
|
<TooltipPrimitive.Root
|
||||||
|
delayDuration={200}
|
||||||
|
open={open}
|
||||||
|
onOpenChange={onOpenChange}
|
||||||
|
>
|
||||||
|
<TooltipPrimitive.Trigger asChild className="tooltip__trigger">
|
||||||
|
{trigger}
|
||||||
|
</TooltipPrimitive.Trigger>
|
||||||
|
<TooltipPrimitive.Portal>
|
||||||
|
{!disabled && content && (
|
||||||
|
<TooltipPrimitive.Content
|
||||||
|
className="tooltip__content"
|
||||||
|
collisionPadding={16}
|
||||||
|
sideOffset={6}
|
||||||
|
side={side}
|
||||||
|
>
|
||||||
|
{content}
|
||||||
|
{withArrow && (
|
||||||
|
<TooltipPrimitive.Arrow className="tooltip__arrow" />
|
||||||
|
)}
|
||||||
|
</TooltipPrimitive.Content>
|
||||||
|
)}
|
||||||
|
</TooltipPrimitive.Portal>
|
||||||
|
</TooltipPrimitive.Root>
|
||||||
|
</TooltipPrimitive.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
82
joi/src/core/Tooltip/styles.scss
Normal file
82
joi/src/core/Tooltip/styles.scss
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
.tooltip {
|
||||||
|
&__content {
|
||||||
|
border-radius: 8px;
|
||||||
|
padding: 8px 14px;
|
||||||
|
line-height: 1;
|
||||||
|
color: hsla(var(--tooltip-fg));
|
||||||
|
background-color: hsla(var(--tooltip-bg));
|
||||||
|
user-select: none;
|
||||||
|
animation-duration: 400ms;
|
||||||
|
animation-timing-function: cubic-bezier(0.16, 1, 0.3, 1);
|
||||||
|
will-change: transform, opacity;
|
||||||
|
font-weight: 500;
|
||||||
|
z-index: 100;
|
||||||
|
max-width: 240px;
|
||||||
|
@apply text-sm leading-normal;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__arrow {
|
||||||
|
fill: hsla(var(--tooltip-bg));
|
||||||
|
}
|
||||||
|
|
||||||
|
&__trigger {
|
||||||
|
@apply cursor-pointer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.tooltip__content[data-state='delayed-open'][data-side='top'] {
|
||||||
|
animation-name: slideDownAndFade;
|
||||||
|
}
|
||||||
|
.tooltip__content[data-state='delayed-open'][data-side='right'] {
|
||||||
|
animation-name: slideLeftAndFade;
|
||||||
|
}
|
||||||
|
.tooltip__content[data-state='delayed-open'][data-side='bottom'] {
|
||||||
|
animation-name: slideUpAndFade;
|
||||||
|
}
|
||||||
|
.tooltip__content[data-state='delayed-open'][data-side='left'] {
|
||||||
|
animation-name: slideRightAndFade;
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideUpAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideRightAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(-4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideDownAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateY(-4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateY(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@keyframes slideLeftAndFade {
|
||||||
|
from {
|
||||||
|
opacity: 0;
|
||||||
|
transform: translateX(4px);
|
||||||
|
}
|
||||||
|
to {
|
||||||
|
opacity: 1;
|
||||||
|
transform: translateX(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -35,7 +35,6 @@ export function useClickOutside<T extends HTMLElement = any>(
|
|||||||
document.removeEventListener(fn, listener)
|
document.removeEventListener(fn, listener)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
|
||||||
}, [ref, handler, nodes])
|
}, [ref, handler, nodes])
|
||||||
|
|
||||||
return ref
|
return ref
|
||||||
34
joi/src/hooks/useClipboard/index.ts
Normal file
34
joi/src/hooks/useClipboard/index.ts
Normal file
@ -0,0 +1,34 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
import { useState } from 'react'
|
||||||
|
|
||||||
|
export function useClipboard({ timeout = 2000 } = {}) {
|
||||||
|
const [error, setError] = useState<Error | null>(null)
|
||||||
|
const [copied, setCopied] = useState(false)
|
||||||
|
const [copyTimeout, setCopyTimeout] = useState<number | null>(null)
|
||||||
|
|
||||||
|
const handleCopyResult = (value: boolean) => {
|
||||||
|
window.clearTimeout(copyTimeout!)
|
||||||
|
setCopyTimeout(window.setTimeout(() => setCopied(false), timeout))
|
||||||
|
setCopied(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
const copy = (valueToCopy: any) => {
|
||||||
|
if ('clipboard' in navigator) {
|
||||||
|
navigator.clipboard
|
||||||
|
.writeText(valueToCopy)
|
||||||
|
.then(() => handleCopyResult(true))
|
||||||
|
.catch((err) => setError(err))
|
||||||
|
} else {
|
||||||
|
setError(new Error('useClipboard: navigator.clipboard is not supported'))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const reset = () => {
|
||||||
|
setCopied(false)
|
||||||
|
setError(null)
|
||||||
|
window.clearTimeout(copyTimeout!)
|
||||||
|
}
|
||||||
|
|
||||||
|
return { copy, reset, error, copied }
|
||||||
|
}
|
||||||
63
joi/src/hooks/useMediaQuery/index.ts
Normal file
63
joi/src/hooks/useMediaQuery/index.ts
Normal file
@ -0,0 +1,63 @@
|
|||||||
|
import { useEffect, useRef, useState } from 'react'
|
||||||
|
|
||||||
|
export interface UseMediaQueryOptions {
|
||||||
|
getInitialValueInEffect: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
type MediaQueryCallback = (event: { matches: boolean; media: string }) => void
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Older versions of Safari (shipped withCatalina and before) do not support addEventListener on matchMedia
|
||||||
|
* https://stackoverflow.com/questions/56466261/matchmedia-addlistener-marked-as-deprecated-addeventlistener-equivalent
|
||||||
|
* */
|
||||||
|
function attachMediaListener(
|
||||||
|
query: MediaQueryList,
|
||||||
|
callback: MediaQueryCallback
|
||||||
|
) {
|
||||||
|
try {
|
||||||
|
query.addEventListener('change', callback)
|
||||||
|
return () => query.removeEventListener('change', callback)
|
||||||
|
} catch (e) {
|
||||||
|
query.addListener(callback)
|
||||||
|
return () => query.removeListener(callback)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function getInitialValue(query: string, initialValue?: boolean) {
|
||||||
|
if (typeof initialValue === 'boolean') {
|
||||||
|
return initialValue
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof window !== 'undefined' && 'matchMedia' in window) {
|
||||||
|
return window.matchMedia(query).matches
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useMediaQuery(
|
||||||
|
query: string,
|
||||||
|
initialValue?: boolean,
|
||||||
|
{ getInitialValueInEffect }: UseMediaQueryOptions = {
|
||||||
|
getInitialValueInEffect: true,
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
const [matches, setMatches] = useState(
|
||||||
|
getInitialValueInEffect ? initialValue : getInitialValue(query)
|
||||||
|
)
|
||||||
|
const queryRef = useRef<MediaQueryList>()
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
if ('matchMedia' in window) {
|
||||||
|
queryRef.current = window.matchMedia(query)
|
||||||
|
setMatches(queryRef.current.matches)
|
||||||
|
return attachMediaListener(queryRef.current, (event) =>
|
||||||
|
setMatches(event.matches)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}, [query])
|
||||||
|
|
||||||
|
return matches
|
||||||
|
}
|
||||||
56
joi/src/hooks/useOs/index.tsx
Normal file
56
joi/src/hooks/useOs/index.tsx
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { useLayoutEffect, useState } from 'react'
|
||||||
|
|
||||||
|
export type OS =
|
||||||
|
| 'undetermined'
|
||||||
|
| 'macos'
|
||||||
|
| 'ios'
|
||||||
|
| 'windows'
|
||||||
|
| 'android'
|
||||||
|
| 'linux'
|
||||||
|
|
||||||
|
function getOS(): OS {
|
||||||
|
if (typeof window === 'undefined') {
|
||||||
|
return 'undetermined'
|
||||||
|
}
|
||||||
|
|
||||||
|
const { userAgent } = window.navigator
|
||||||
|
const macosPlatforms = /(Macintosh)|(MacIntel)|(MacPPC)|(Mac68K)/i
|
||||||
|
const windowsPlatforms = /(Win32)|(Win64)|(Windows)|(WinCE)/i
|
||||||
|
const iosPlatforms = /(iPhone)|(iPad)|(iPod)/i
|
||||||
|
|
||||||
|
if (macosPlatforms.test(userAgent)) {
|
||||||
|
return 'macos'
|
||||||
|
}
|
||||||
|
if (iosPlatforms.test(userAgent)) {
|
||||||
|
return 'ios'
|
||||||
|
}
|
||||||
|
if (windowsPlatforms.test(userAgent)) {
|
||||||
|
return 'windows'
|
||||||
|
}
|
||||||
|
if (/Android/i.test(userAgent)) {
|
||||||
|
return 'android'
|
||||||
|
}
|
||||||
|
if (/Linux/i.test(userAgent)) {
|
||||||
|
return 'linux'
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'undetermined'
|
||||||
|
}
|
||||||
|
|
||||||
|
interface UseOsOptions {
|
||||||
|
getValueInEffect: boolean
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useOs(options: UseOsOptions = { getValueInEffect: true }): OS {
|
||||||
|
const [value, setValue] = useState<OS>(
|
||||||
|
options.getValueInEffect ? 'undetermined' : getOS()
|
||||||
|
)
|
||||||
|
|
||||||
|
useLayoutEffect(() => {
|
||||||
|
if (options.getValueInEffect) {
|
||||||
|
setValue(getOS)
|
||||||
|
}
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return value
|
||||||
|
}
|
||||||
9
joi/src/hooks/usePageLeave/index.ts
Normal file
9
joi/src/hooks/usePageLeave/index.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
import { useEffect } from 'react'
|
||||||
|
|
||||||
|
export function usePageLeave(onPageLeave: () => void) {
|
||||||
|
useEffect(() => {
|
||||||
|
document.documentElement.addEventListener('mouseleave', onPageLeave)
|
||||||
|
return () =>
|
||||||
|
document.documentElement.removeEventListener('mouseleave', onPageLeave)
|
||||||
|
}, [])
|
||||||
|
}
|
||||||
24
joi/src/hooks/useTextSelection/index.ts
Normal file
24
joi/src/hooks/useTextSelection/index.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { useEffect, useState } from 'react'
|
||||||
|
|
||||||
|
import { useReducer } from 'react'
|
||||||
|
|
||||||
|
const reducer = (value: number) => (value + 1) % 1000000
|
||||||
|
|
||||||
|
export function useTextSelection(): Selection | null {
|
||||||
|
const [, update] = useReducer(reducer, 0)
|
||||||
|
const [selection, setSelection] = useState<Selection | null>(null)
|
||||||
|
|
||||||
|
const handleSelectionChange = () => {
|
||||||
|
setSelection(document.getSelection())
|
||||||
|
update()
|
||||||
|
}
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
setSelection(document.getSelection())
|
||||||
|
document.addEventListener('selectionchange', handleSelectionChange)
|
||||||
|
return () =>
|
||||||
|
document.removeEventListener('selectionchange', handleSelectionChange)
|
||||||
|
}, [])
|
||||||
|
|
||||||
|
return selection
|
||||||
|
}
|
||||||
21
joi/src/index.ts
Normal file
21
joi/src/index.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
export * from './core/Tooltip'
|
||||||
|
export * from './core/ScrollArea'
|
||||||
|
export * from './core/Button'
|
||||||
|
export * from './core/Switch'
|
||||||
|
export * from './core/Progress'
|
||||||
|
export * from './core/Checkbox'
|
||||||
|
export * from './core/Badge'
|
||||||
|
export * from './core/Modal'
|
||||||
|
export * from './core/Slider'
|
||||||
|
export * from './core/Input'
|
||||||
|
export * from './core/Select'
|
||||||
|
export * from './core/TextArea'
|
||||||
|
export * from './core/Tabs'
|
||||||
|
export * from './core/Accordion'
|
||||||
|
|
||||||
|
export * from './hooks/useClipboard'
|
||||||
|
export * from './hooks/usePageLeave'
|
||||||
|
export * from './hooks/useTextSelection'
|
||||||
|
export * from './hooks/useClickOutside'
|
||||||
|
export * from './hooks/useOs'
|
||||||
|
export * from './hooks/useMediaQuery'
|
||||||
10
joi/tailwind.config.js
Normal file
10
joi/tailwind.config.js
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
/** @type {import('tailwindcss').Config} */
|
||||||
|
|
||||||
|
// eslint-disable-next-line no-undef
|
||||||
|
module.exports = {
|
||||||
|
content: ['./src/**/*.{js,ts,jsx,tsx,mdx}'],
|
||||||
|
theme: {
|
||||||
|
extend: {},
|
||||||
|
},
|
||||||
|
plugins: [],
|
||||||
|
}
|
||||||
16
joi/tsconfig.json
Normal file
16
joi/tsconfig.json
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "esnext",
|
||||||
|
"declaration": true,
|
||||||
|
"declarationDir": "dist/types",
|
||||||
|
"module": "esnext",
|
||||||
|
"lib": ["es6", "dom", "es2016", "es2017"],
|
||||||
|
"sourceMap": true,
|
||||||
|
"jsx": "react",
|
||||||
|
"moduleResolution": "Node",
|
||||||
|
"allowSyntheticDefaultImports": true,
|
||||||
|
"esModuleInterop": true
|
||||||
|
},
|
||||||
|
"include": ["src/**/*"],
|
||||||
|
"exclude": ["node_modules"]
|
||||||
|
}
|
||||||
12
package.json
12
package.json
@ -3,14 +3,14 @@
|
|||||||
"private": true,
|
"private": true,
|
||||||
"workspaces": {
|
"workspaces": {
|
||||||
"packages": [
|
"packages": [
|
||||||
"uikit",
|
"joi",
|
||||||
"core",
|
"core",
|
||||||
"electron",
|
"electron",
|
||||||
"web",
|
"web",
|
||||||
"server"
|
"server"
|
||||||
],
|
],
|
||||||
"nohoist": [
|
"nohoist": [
|
||||||
"uikit",
|
"joi",
|
||||||
"core",
|
"core",
|
||||||
"electron",
|
"electron",
|
||||||
"web",
|
"web",
|
||||||
@ -26,13 +26,11 @@
|
|||||||
"pre-install:linux": "find extensions -type f -path \"**/*.tgz\" -exec cp {} pre-install \\;",
|
"pre-install:linux": "find extensions -type f -path \"**/*.tgz\" -exec cp {} pre-install \\;",
|
||||||
"pre-install:win32": "powershell -Command \"Get-ChildItem -Path \"extensions\" -Recurse -File -Filter \"*.tgz\" | ForEach-Object { Copy-Item -Path $_.FullName -Destination \"pre-install\" }\"",
|
"pre-install:win32": "powershell -Command \"Get-ChildItem -Path \"extensions\" -Recurse -File -Filter \"*.tgz\" | ForEach-Object { Copy-Item -Path $_.FullName -Destination \"pre-install\" }\"",
|
||||||
"pre-install": "run-script-os",
|
"pre-install": "run-script-os",
|
||||||
"copy:assets": "cpx \"pre-install/*.tgz\" \"electron/pre-install/\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"",
|
"copy:assets": "cpx \"pre-install/*.tgz\" \"electron/pre-install/\" && cpx \"themes/**\" \"electron/themes\" && cpx \"docs/openapi/**\" \"electron/docs/openapi\"",
|
||||||
"dev:electron": "yarn copy:assets && yarn workspace jan dev",
|
"dev:electron": "yarn copy:assets && yarn workspace jan dev",
|
||||||
"dev:web": "yarn workspace @janhq/web dev",
|
"dev:web": "yarn workspace @janhq/web dev",
|
||||||
"dev:server": "yarn copy:assets && yarn workspace @janhq/server dev",
|
"dev:server": "yarn copy:assets && yarn workspace @janhq/server dev",
|
||||||
"dev": "turbo run dev --parallel --filter=!@janhq/server",
|
"dev": "turbo run dev --parallel --filter=!@janhq/server",
|
||||||
"dev:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit dev",
|
|
||||||
"build:uikit": "yarn workspace @janhq/uikit install && yarn workspace @janhq/uikit build",
|
|
||||||
"build:server": "yarn copy:assets && cd server && yarn install && yarn run build",
|
"build:server": "yarn copy:assets && cd server && yarn install && yarn run build",
|
||||||
"build:core": "cd core && yarn install && yarn run build",
|
"build:core": "cd core && yarn install && yarn run build",
|
||||||
"build:web": "yarn workspace @janhq/web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
"build:web": "yarn workspace @janhq/web build && cpx \"web/out/**\" \"electron/renderer/\"",
|
||||||
@ -41,7 +39,9 @@
|
|||||||
"build:extensions": "rimraf ./pre-install/*.tgz && turbo run @janhq/core#build && cd extensions && yarn install && turbo run build:publish && cd .. && yarn pre-install",
|
"build:extensions": "rimraf ./pre-install/*.tgz && turbo run @janhq/core#build && cd extensions && yarn install && turbo run build:publish && cd .. && yarn pre-install",
|
||||||
"build:test": "yarn copy:assets && turbo run @janhq/web#build && cpx \"web/out/**\" \"electron/renderer/\" && turbo run build:test",
|
"build:test": "yarn copy:assets && turbo run @janhq/web#build && cpx \"web/out/**\" \"electron/renderer/\" && turbo run build:test",
|
||||||
"build": "yarn build:web && yarn build:electron",
|
"build": "yarn build:web && yarn build:electron",
|
||||||
"build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish"
|
"build:publish": "yarn copy:assets && yarn build:web && yarn workspace jan build:publish",
|
||||||
|
"dev:joi": "yarn workspace @janhq/joi install && yarn workspace @janhq/joi dev",
|
||||||
|
"build:joi": "yarn workspace @janhq/joi install && yarn workspace @janhq/joi build"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"concurrently": "^8.2.1",
|
"concurrently": "^8.2.1",
|
||||||
|
|||||||
144
themes/dark-dimmed/theme.json
Normal file
144
themes/dark-dimmed/theme.json
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"id": "dark-dimmed",
|
||||||
|
"displayName": "Dark Dimmed",
|
||||||
|
"reduceTransparent": false,
|
||||||
|
"nativeTheme": "dark",
|
||||||
|
"variables": {
|
||||||
|
"app": {
|
||||||
|
"bg": "215, 25%, 9%, 1",
|
||||||
|
"transparent": "0, 0%, 13%, 0.3",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"link": "221, 79%, 59%, 1",
|
||||||
|
"code-block": "0, 0%, 10%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"primary": {
|
||||||
|
"bg": {
|
||||||
|
"__default": "175, 84%, 32%, 1",
|
||||||
|
"soft": "175, 84%, 10%, 1"
|
||||||
|
},
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"secondary": {
|
||||||
|
"bg": "0, 0%, 100%, 0.2",
|
||||||
|
"fg": "0, 0%, 80%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tertiary": {
|
||||||
|
"bg": "0, 0%, 0%, 0.02"
|
||||||
|
},
|
||||||
|
|
||||||
|
"disabled": {
|
||||||
|
"bg": "0, 0%, 0%, 0.2",
|
||||||
|
"fg": "0, 0%, 100%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"top-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"bottom-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ribbon-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"icon-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"icon-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "175, 84%, 32%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"left-panel": {
|
||||||
|
"bg": "215, 25%, 9%, 1",
|
||||||
|
"menu": "0, 0%, 95%, 1",
|
||||||
|
"menu-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"menu-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "175, 84%, 20%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"center-panel": {
|
||||||
|
"bg": "215, 25%, 9%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"right-panel": {
|
||||||
|
"bg": "215, 25%, 9%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tooltip": {
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"fg": "0, 0%, 0%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
"bg": "0, 0%, 80%, 0.1",
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dropdown-menu": {
|
||||||
|
"hover-bg": "0, 0%, 28%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scrollbar": {
|
||||||
|
"tracker": "0, 0%, 28%, 0.2",
|
||||||
|
"thumb": "0, 0%, 20%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"input": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
"input-bg": "0, 0%, 13%, 0",
|
||||||
|
"bg": "215, 25%, 9%, 1",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"options-active-bg": "0, 0%, 28%, 0.2",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"progress-track": {
|
||||||
|
"bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"modal": {
|
||||||
|
"overlay": "0, 0%, 0%, 0.5",
|
||||||
|
"bg": "215, 25%, 9%, 1",
|
||||||
|
"fg": "0, 0%, 100%, 11"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loader": {
|
||||||
|
"bg": "221, 83%, 53%, 0.1",
|
||||||
|
"fg": "0, 0%, 80%, 1",
|
||||||
|
"active-bg": "221, 83%, 53%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"toaster": {
|
||||||
|
"bg": "240, 6%, 10%, 1",
|
||||||
|
"text-title": "0, 0%, 100%, 1",
|
||||||
|
"close-icon": "0, 0%, 100%, 0.8",
|
||||||
|
"text-desc": "0, 0%, 100%, 0.88"
|
||||||
|
},
|
||||||
|
|
||||||
|
"slider": {
|
||||||
|
"track-bg": "0, 0%, 100%, 0.1",
|
||||||
|
"thumb-bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"resize": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
themes/joi-dark/theme.json
Normal file
144
themes/joi-dark/theme.json
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"id": "joi-dark",
|
||||||
|
"displayName": "Joi Dark",
|
||||||
|
"reduceTransparent": true,
|
||||||
|
"nativeTheme": "dark",
|
||||||
|
"variables": {
|
||||||
|
"app": {
|
||||||
|
"bg": "0, 0%, 13%, 1",
|
||||||
|
"transparent": "0, 0%, 13%, 0.3",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"link": "221, 83%, 53%, 1",
|
||||||
|
"code-block": "0, 0%, 17%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"primary": {
|
||||||
|
"bg": {
|
||||||
|
"__default": "221, 79%, 59%, 1",
|
||||||
|
"soft": "221, 79%, 59%, 0.2"
|
||||||
|
},
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"secondary": {
|
||||||
|
"bg": "0, 0%, 100%, 0.2",
|
||||||
|
"fg": "0, 0%, 80%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tertiary": {
|
||||||
|
"bg": "0, 0%, 0%, 0.02"
|
||||||
|
},
|
||||||
|
|
||||||
|
"disabled": {
|
||||||
|
"bg": "0, 0%, 0%, 0.2",
|
||||||
|
"fg": "0, 0%, 100%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"top-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"bottom-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ribbon-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0.3",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"icon-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"icon-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"left-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"menu": "0, 0%, 95%, 1",
|
||||||
|
"menu-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"menu-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"center-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"right-panel": {
|
||||||
|
"bg": "0, 0%, 13%, 0"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tooltip": {
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"fg": "0, 0%, 0%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
"bg": "0, 0%, 80%, 0.1",
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dropdown-menu": {
|
||||||
|
"hover-bg": "0, 0%, 28%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scrollbar": {
|
||||||
|
"tracker": "0, 0%, 28%, 0.2",
|
||||||
|
"thumb": "0, 0%, 50%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"input": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
"input-bg": "0, 0%, 13%, 0",
|
||||||
|
"bg": "0, 0%, 13%, 1",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"options-active-bg": "0, 0%, 28%, 0.2",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"progress-track": {
|
||||||
|
"bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"modal": {
|
||||||
|
"overlay": "0, 0%, 0%, 0.7",
|
||||||
|
"bg": "0, 0%, 13%, 1",
|
||||||
|
"fg": "0, 0%, 100%, 11"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loader": {
|
||||||
|
"bg": "221, 83%, 53%, 0.1",
|
||||||
|
"fg": "0, 0%, 80%, 1",
|
||||||
|
"active-bg": "221, 83%, 53%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"toaster": {
|
||||||
|
"bg": "240, 6%, 10%, 1",
|
||||||
|
"text-title": "0, 0%, 100%, 1",
|
||||||
|
"close-icon": "0, 0%, 100%, 0.8",
|
||||||
|
"text-desc": "0, 0%, 100%, 0.88"
|
||||||
|
},
|
||||||
|
|
||||||
|
"slider": {
|
||||||
|
"track-bg": "0, 0%, 100%, 0.1",
|
||||||
|
"thumb-bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"resize": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
themes/joi-light/theme.json
Normal file
144
themes/joi-light/theme.json
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"id": "joi-light",
|
||||||
|
"displayName": "Joi Light",
|
||||||
|
"reduceTransparent": true,
|
||||||
|
"nativeTheme": "light",
|
||||||
|
"variables": {
|
||||||
|
"app": {
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"transparent": "0, 0%, 100%, 0.8",
|
||||||
|
"border": "0, 0%, 0%, 0.1",
|
||||||
|
"link": "221, 83%, 53%, 1",
|
||||||
|
"code-block": "0, 0%, 17%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"primary": {
|
||||||
|
"bg": {
|
||||||
|
"__default": "221, 83%, 53%, 1",
|
||||||
|
"soft": "221, 83%, 53%, 0.1"
|
||||||
|
},
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"secondary": {
|
||||||
|
"bg": " 0, 0%, 0%, 0.1",
|
||||||
|
"fg": " 0, 0%, 0%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tertiary": {
|
||||||
|
"bg": "0, 0%, 0%, 0.02"
|
||||||
|
},
|
||||||
|
|
||||||
|
"disabled": {
|
||||||
|
"bg": " 0, 0%, 0%, 0.05",
|
||||||
|
"fg": " 0, 0%, 0%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"top-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 0.8"
|
||||||
|
},
|
||||||
|
|
||||||
|
"bottom-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 0.8"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ribbon-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 0.8",
|
||||||
|
"border": "0, 0%, 0%, 0.1",
|
||||||
|
"icon": "0, 0%, 0%, 0.5",
|
||||||
|
"icon-hover": "0, 0%, 0%, 0.03",
|
||||||
|
"icon-active": "0, 0%, 0%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 0%, 0.05"
|
||||||
|
},
|
||||||
|
|
||||||
|
"left-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"menu": "0, 0%, 0%, 0.8",
|
||||||
|
"menu-hover": "0, 0%, 0%, 0.03",
|
||||||
|
"menu-active": "0, 0%, 0%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 0%, 0.05"
|
||||||
|
},
|
||||||
|
|
||||||
|
"center-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"right-panel": {
|
||||||
|
"bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tooltip": {
|
||||||
|
"bg": "0, 0%, 0%, 1",
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1",
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dropdown-menu": {
|
||||||
|
"hover-bg": "0, 0%, 0%, 0.03"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scrollbar": {
|
||||||
|
"tracker": "0, 0%, 95%, 0.1",
|
||||||
|
"thumb": "0, 0%, 0%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"input": {
|
||||||
|
"bg": "0, 0%, 100%, 0",
|
||||||
|
"placeholder": "0, 0%, 0%, 0.5",
|
||||||
|
"icon": "0, 0%, 0%, 0.5",
|
||||||
|
"border": "0, 0%, 0%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
"bg": "0, 0%, 100%, 0",
|
||||||
|
"placeholder": "0, 0%, 0%, 0.5",
|
||||||
|
"icon": "0, 0%, 0%, 0.5",
|
||||||
|
"border": "0, 0%, 0%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
"input-bg": "0, 0%, 100%, 0",
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"placeholder": "0, 0%, 0%, 0.5",
|
||||||
|
"icon": "0, 0%, 0%, 0.5",
|
||||||
|
"options-active-bg": "0, 0%, 0%, 0.03",
|
||||||
|
"border": "0, 0%, 0%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"progress-track": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"modal": {
|
||||||
|
"overlay": "0, 0%, 0%, 0.5",
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"fg": "0, 0%, 0%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loader": {
|
||||||
|
"bg": "221, 83%, 53%, 0.1",
|
||||||
|
"fg": "221, 83%, 53%, 1",
|
||||||
|
"active-bg": "221, 83%, 53%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"toaster": {
|
||||||
|
"bg": "240, 6%, 10%, 1",
|
||||||
|
"text-title": "0, 0%, 100%, 1",
|
||||||
|
"close-icon": "0, 0%, 100%, 0.8",
|
||||||
|
"text-desc": "0, 0%, 100%, 0.8"
|
||||||
|
},
|
||||||
|
|
||||||
|
"slider": {
|
||||||
|
"track-bg": "0, 0%, 0%, 0.1",
|
||||||
|
"thumb-bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"resize": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
144
themes/night-blue/theme.json
Normal file
144
themes/night-blue/theme.json
Normal file
@ -0,0 +1,144 @@
|
|||||||
|
{
|
||||||
|
"id": "night-blue",
|
||||||
|
"displayName": "Night Blue",
|
||||||
|
"reduceTransparent": false,
|
||||||
|
"nativeTheme": "dark",
|
||||||
|
"variables": {
|
||||||
|
"app": {
|
||||||
|
"bg": "211, 100%, 15%, 1",
|
||||||
|
"transparent": "221, 79%, 59%, 0.08",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"link": "142, 76%, 36%, 1",
|
||||||
|
"code-block": "222, 96%, 10%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"primary": {
|
||||||
|
"bg": {
|
||||||
|
"__default": "221, 79%, 59%, 1",
|
||||||
|
"soft": "221, 79%, 59%, 0.2"
|
||||||
|
},
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"secondary": {
|
||||||
|
"bg": "0, 0%, 100%, 0.2",
|
||||||
|
"fg": "0, 0%, 80%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tertiary": {
|
||||||
|
"bg": "0, 0%, 0%, 0.02"
|
||||||
|
},
|
||||||
|
|
||||||
|
"disabled": {
|
||||||
|
"bg": "0, 0%, 0%, 0.2",
|
||||||
|
"fg": "0, 0%, 100%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"top-panel": {
|
||||||
|
"bg": "211, 100%, 9%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"bottom-panel": {
|
||||||
|
"bg": "211, 100%, 9%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"ribbon-panel": {
|
||||||
|
"bg": "211, 100%, 10%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"icon-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"icon-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"left-panel": {
|
||||||
|
"bg": "211, 100%, 12%, 1",
|
||||||
|
"menu": "0, 0%, 95%, 1",
|
||||||
|
"menu-hover": "0, 0%, 28%, 0.2",
|
||||||
|
"menu-active": "0, 0%, 100%, 1",
|
||||||
|
"icon-active-bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"center-panel": {
|
||||||
|
"bg": "211, 100%, 15%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"right-panel": {
|
||||||
|
"bg": "211, 100%, 12%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"tooltip": {
|
||||||
|
"bg": "0, 0%, 100%, 1",
|
||||||
|
"fg": "0, 0%, 0%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"switch": {
|
||||||
|
"bg": "0, 0%, 80%, 0.1",
|
||||||
|
"fg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"dropdown-menu": {
|
||||||
|
"hover-bg": "0, 0%, 28%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"scrollbar": {
|
||||||
|
"tracker": "0, 0%, 28%, 0.2",
|
||||||
|
"thumb": "0, 0%, 50%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"input": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"textarea": {
|
||||||
|
"bg": "0, 0%, 13%, 0",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"select": {
|
||||||
|
"input-bg": "0, 0%, 13%, 0",
|
||||||
|
"bg": "211, 100%, 15%, 1",
|
||||||
|
"placeholder": "0, 0%, 70%, 0.5",
|
||||||
|
"icon": "0, 0%, 68%, 1",
|
||||||
|
"options-active-bg": "0, 0%, 28%, 0.2",
|
||||||
|
"border": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"progress-track": {
|
||||||
|
"bg": "0, 0%, 100%, 0.1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"modal": {
|
||||||
|
"overlay": "0, 0%, 0%, 0.5",
|
||||||
|
"bg": "222, 96%, 16%, 1",
|
||||||
|
"fg": "0, 0%, 100%, 11"
|
||||||
|
},
|
||||||
|
|
||||||
|
"loader": {
|
||||||
|
"bg": "221, 83%, 53%, 0.1",
|
||||||
|
"fg": "0, 0%, 80%, 1",
|
||||||
|
"active-bg": "221, 83%, 53%, 0.2"
|
||||||
|
},
|
||||||
|
|
||||||
|
"toaster": {
|
||||||
|
"bg": "222, 100%, 15%, 1",
|
||||||
|
"text-title": "0, 0%, 100%, 1",
|
||||||
|
"close-icon": "0, 0%, 100%, 0.8",
|
||||||
|
"text-desc": "0, 0%, 100%, 0.88"
|
||||||
|
},
|
||||||
|
|
||||||
|
"slider": {
|
||||||
|
"track-bg": "0, 0%, 100%, 0.1",
|
||||||
|
"thumb-bg": "0, 0%, 100%, 1"
|
||||||
|
},
|
||||||
|
|
||||||
|
"resize": {
|
||||||
|
"bg": "0, 0%, 0%, 0.1"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -10,7 +10,7 @@
|
|||||||
"@janhq/web#dev": {
|
"@janhq/web#dev": {
|
||||||
"cache": false,
|
"cache": false,
|
||||||
"persistent": true,
|
"persistent": true,
|
||||||
"dependsOn": ["@janhq/core#build", "@janhq/uikit#build"]
|
"dependsOn": ["@janhq/core#build", "@janhq/joi#build"]
|
||||||
},
|
},
|
||||||
"@janhq/server#build": {
|
"@janhq/server#build": {
|
||||||
"outputs": ["dist/**"],
|
"outputs": ["dist/**"],
|
||||||
@ -26,7 +26,7 @@
|
|||||||
},
|
},
|
||||||
"@janhq/web#build": {
|
"@janhq/web#build": {
|
||||||
"outputs": ["out/**"],
|
"outputs": ["out/**"],
|
||||||
"dependsOn": ["@janhq/core#build", "@janhq/uikit#build"]
|
"dependsOn": ["@janhq/core#build", "@janhq/joi#build"]
|
||||||
},
|
},
|
||||||
"jan#build": {
|
"jan#build": {
|
||||||
"outputs": ["dist/**"],
|
"outputs": ["dist/**"],
|
||||||
|
|||||||
@ -1,57 +0,0 @@
|
|||||||
{
|
|
||||||
"name": "@janhq/uikit",
|
|
||||||
"version": "0.1.0",
|
|
||||||
"license": "MIT",
|
|
||||||
"main": "./dist/index.js",
|
|
||||||
"module": "./dist/index.mjs",
|
|
||||||
"types": "./dist/index.d.ts",
|
|
||||||
"files": [
|
|
||||||
"dist/**"
|
|
||||||
],
|
|
||||||
"scripts": {
|
|
||||||
"build:styles": "postcss src/main.scss -o dist/index.css --use postcss-import",
|
|
||||||
"build:react": "tsup src/index.{ts,tsx} --format cjs,esm --dts --external react react-dom --minify terser --splitting --sourcemap",
|
|
||||||
"dev:react": "tsup src/index.{ts,tsx} --format cjs,esm --watch --dts",
|
|
||||||
"dev:styles": "postcss src/main.scss -o dist/index.css -u postcss-import -w",
|
|
||||||
"build": "yarn build:styles && yarn build:react",
|
|
||||||
"dev": "concurrently --kill-others \"yarn dev:styles\" \"yarn dev:react\""
|
|
||||||
},
|
|
||||||
"dependencies": {
|
|
||||||
"@radix-ui/react-avatar": "^1.0.4",
|
|
||||||
"@radix-ui/react-checkbox": "^1.0.4",
|
|
||||||
"@radix-ui/react-context": "^1.0.1",
|
|
||||||
"@radix-ui/react-dialog": "^1.0.5",
|
|
||||||
"@radix-ui/react-icons": "^1.3.0",
|
|
||||||
"@radix-ui/react-label": "^2.0.2",
|
|
||||||
"@radix-ui/react-progress": "^1.0.3",
|
|
||||||
"@radix-ui/react-scroll-area": "^1.0.5",
|
|
||||||
"@radix-ui/react-select": "^2.0.0",
|
|
||||||
"@radix-ui/react-slider": "^1.1.2",
|
|
||||||
"@radix-ui/react-slot": "^1.0.2",
|
|
||||||
"@radix-ui/react-switch": "^1.0.3",
|
|
||||||
"@radix-ui/react-toast": "^1.1.5",
|
|
||||||
"@radix-ui/react-tooltip": "^1.0.7",
|
|
||||||
"autoprefixer": "^10.4.16",
|
|
||||||
"class-variance-authority": "^0.7.0",
|
|
||||||
"cmdk": "^0.2.0",
|
|
||||||
"lucide-react": "^0.292.0",
|
|
||||||
"postcss": "^8.4.31",
|
|
||||||
"react": "^18.2.0",
|
|
||||||
"react-dom": "^18.2.0",
|
|
||||||
"react-hook-form": "^7.47.0",
|
|
||||||
"scss": "^0.2.4",
|
|
||||||
"tailwindcss": "^3.3.5"
|
|
||||||
},
|
|
||||||
"devDependencies": {
|
|
||||||
"concurrently": "^8.2.2",
|
|
||||||
"postcss-cli": "^10.1.0",
|
|
||||||
"postcss-import": "^15.1.0",
|
|
||||||
"prejss-cli": "^0.3.3",
|
|
||||||
"prettier": "^3.0.3",
|
|
||||||
"prettier-plugin-tailwindcss": "^0.5.6",
|
|
||||||
"tailwind-merge": "^2.0.0",
|
|
||||||
"terser": "^5.24.0",
|
|
||||||
"tsup": "^7.2.0",
|
|
||||||
"typescript": "^5.3.3"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,8 +0,0 @@
|
|||||||
module.exports = {
|
|
||||||
plugins: {
|
|
||||||
"tailwindcss/nesting": {},
|
|
||||||
tailwindcss: {},
|
|
||||||
autoprefixer: {},
|
|
||||||
"postcss-import": {},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
@ -1,43 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { forwardRef, ElementRef, ComponentPropsWithoutRef } from 'react'
|
|
||||||
import * as AvatarPrimitive from '@radix-ui/react-avatar'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Avatar = forwardRef<
|
|
||||||
ElementRef<typeof AvatarPrimitive.Root>,
|
|
||||||
ComponentPropsWithoutRef<typeof AvatarPrimitive.Root>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<AvatarPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('avatar', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
Avatar.displayName = AvatarPrimitive.Root.displayName
|
|
||||||
|
|
||||||
const AvatarImage = forwardRef<
|
|
||||||
ElementRef<typeof AvatarPrimitive.Image>,
|
|
||||||
ComponentPropsWithoutRef<typeof AvatarPrimitive.Image>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<AvatarPrimitive.Image
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('avatar-image', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
AvatarImage.displayName = AvatarPrimitive.Image.displayName
|
|
||||||
|
|
||||||
const AvatarFallback = forwardRef<
|
|
||||||
ElementRef<typeof AvatarPrimitive.Fallback>,
|
|
||||||
ComponentPropsWithoutRef<typeof AvatarPrimitive.Fallback>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<AvatarPrimitive.Fallback
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('avatar-fallback', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName
|
|
||||||
|
|
||||||
export { Avatar, AvatarImage, AvatarFallback }
|
|
||||||
@ -1,11 +0,0 @@
|
|||||||
.avatar {
|
|
||||||
@apply relative flex h-10 w-10 shrink-0 overflow-hidden rounded-full;
|
|
||||||
|
|
||||||
&-image {
|
|
||||||
@apply aspect-square h-full w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-fallback {
|
|
||||||
@apply bg-muted flex h-full w-full items-center justify-center rounded-full font-bold uppercase;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import { cva, type VariantProps } from 'class-variance-authority'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const badgeVariants = cva('badge', {
|
|
||||||
variants: {
|
|
||||||
themes: {
|
|
||||||
primary: 'badge-primary',
|
|
||||||
warning: 'badge-warning',
|
|
||||||
success: 'badge-success',
|
|
||||||
secondary: 'badge-secondary',
|
|
||||||
danger: 'badge-danger',
|
|
||||||
outline: 'badge-outline',
|
|
||||||
pink: 'badge-pink',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
themes: 'primary',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface BadgeProps
|
|
||||||
extends React.HTMLAttributes<HTMLDivElement>,
|
|
||||||
VariantProps<typeof badgeVariants> {}
|
|
||||||
|
|
||||||
function Badge({ className, themes, ...props }: BadgeProps) {
|
|
||||||
return (
|
|
||||||
<div className={twMerge(badgeVariants({ themes }), className)} {...props} />
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
export { Badge, badgeVariants }
|
|
||||||
@ -1,31 +0,0 @@
|
|||||||
.badge {
|
|
||||||
@apply focus:ring-ring border-border inline-flex items-center rounded-md border px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none focus:ring-2 focus:ring-offset-2;
|
|
||||||
|
|
||||||
&-primary {
|
|
||||||
@apply border-transparent bg-blue-100 text-blue-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-pink {
|
|
||||||
@apply border-transparent bg-pink-100 text-pink-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-success {
|
|
||||||
@apply border-transparent bg-green-100 text-green-600;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-secondary {
|
|
||||||
@apply bg-secondary text-secondary-foreground hover:bg-secondary/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-danger {
|
|
||||||
@apply border-transparent bg-red-100 text-red-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-warning {
|
|
||||||
@apply border-transparent bg-yellow-100 text-yellow-700;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-outline {
|
|
||||||
@apply text-foreground border-border border;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,101 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { forwardRef, ButtonHTMLAttributes } from 'react'
|
|
||||||
import { Slot } from '@radix-ui/react-slot'
|
|
||||||
import { cva, type VariantProps } from 'class-variance-authority'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const buttonVariants = cva('btn', {
|
|
||||||
variants: {
|
|
||||||
themes: {
|
|
||||||
primary: 'btn-primary',
|
|
||||||
danger: 'btn-danger',
|
|
||||||
outline: 'btn-outline',
|
|
||||||
secondary: 'btn-secondary',
|
|
||||||
secondaryBlue: 'btn-secondary-blue',
|
|
||||||
secondaryDanger: 'btn-secondary-danger',
|
|
||||||
ghost: 'btn-ghost',
|
|
||||||
success: 'btn-success',
|
|
||||||
},
|
|
||||||
size: {
|
|
||||||
sm: 'btn-sm',
|
|
||||||
md: 'btn-md',
|
|
||||||
lg: 'btn-lg',
|
|
||||||
},
|
|
||||||
block: {
|
|
||||||
true: 'w-full',
|
|
||||||
},
|
|
||||||
loading: {
|
|
||||||
true: 'btn-loading',
|
|
||||||
},
|
|
||||||
},
|
|
||||||
defaultVariants: {
|
|
||||||
themes: 'primary',
|
|
||||||
size: 'md',
|
|
||||||
},
|
|
||||||
})
|
|
||||||
|
|
||||||
export interface ButtonProps
|
|
||||||
extends ButtonHTMLAttributes<HTMLButtonElement>,
|
|
||||||
VariantProps<typeof buttonVariants> {
|
|
||||||
asChild?: boolean
|
|
||||||
}
|
|
||||||
|
|
||||||
const Button = forwardRef<HTMLButtonElement, ButtonProps>(
|
|
||||||
(
|
|
||||||
{
|
|
||||||
className,
|
|
||||||
themes,
|
|
||||||
size,
|
|
||||||
block,
|
|
||||||
loading,
|
|
||||||
asChild = false,
|
|
||||||
children,
|
|
||||||
...props
|
|
||||||
},
|
|
||||||
ref
|
|
||||||
) => {
|
|
||||||
const Comp = asChild ? Slot : 'button'
|
|
||||||
return (
|
|
||||||
<Comp
|
|
||||||
className={twMerge(
|
|
||||||
buttonVariants({ themes, size, block, loading, className })
|
|
||||||
)}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{loading ? (
|
|
||||||
<>
|
|
||||||
<svg
|
|
||||||
aria-hidden="true"
|
|
||||||
role="status"
|
|
||||||
className="btn-loading-circle"
|
|
||||||
xmlns="http://www.w3.org/2000/svg"
|
|
||||||
fill="none"
|
|
||||||
viewBox="0 0 24 24"
|
|
||||||
>
|
|
||||||
<circle
|
|
||||||
cx="12"
|
|
||||||
cy="12"
|
|
||||||
r="10"
|
|
||||||
stroke="currentColor"
|
|
||||||
strokeWidth="4"
|
|
||||||
></circle>
|
|
||||||
<path
|
|
||||||
fill="currentColor"
|
|
||||||
d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z"
|
|
||||||
></path>
|
|
||||||
</svg>
|
|
||||||
{children}
|
|
||||||
</>
|
|
||||||
) : (
|
|
||||||
children
|
|
||||||
)}
|
|
||||||
</Comp>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Button.displayName = 'Button'
|
|
||||||
|
|
||||||
export { Button, buttonVariants }
|
|
||||||
@ -1,84 +0,0 @@
|
|||||||
.btn {
|
|
||||||
@apply inline-flex items-center justify-center whitespace-nowrap rounded-lg font-semibold transition-colors;
|
|
||||||
@apply cursor-pointer;
|
|
||||||
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
|
||||||
|
|
||||||
&-primary {
|
|
||||||
@apply bg-primary hover:bg-primary/90 text-white;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-secondary-blue {
|
|
||||||
@apply bg-blue-200 text-blue-600 hover:bg-blue-300/50 dark:hover:bg-blue-200/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-danger {
|
|
||||||
@apply bg-danger text-danger-foreground hover:bg-danger/90;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-secondary-danger {
|
|
||||||
@apply bg-red-200 text-red-600 hover:bg-red-300/50 dark:hover:bg-red-200/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-outline {
|
|
||||||
@apply border-input border bg-transparent;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-secondary {
|
|
||||||
@apply bg-secondary text-secondary-foreground hover:bg-secondary/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-success {
|
|
||||||
@apply bg-green-500 text-white hover:bg-green-500/80;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-ghost {
|
|
||||||
@apply hover:bg-secondary hover:text-secondary-foreground;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-sm {
|
|
||||||
@apply h-7 rounded-md px-3 text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-md {
|
|
||||||
@apply h-9 px-4 py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-lg {
|
|
||||||
@apply h-10 rounded-md px-8;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-loading {
|
|
||||||
@apply pointer-events-none opacity-50;
|
|
||||||
&-circle {
|
|
||||||
@apply mr-2 h-4 animate-spin opacity-50;
|
|
||||||
> circle {
|
|
||||||
opacity: 0.25;
|
|
||||||
}
|
|
||||||
> path {
|
|
||||||
opacity: 0.75;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
[type='button'],
|
|
||||||
[type='reset'],
|
|
||||||
[type='submit'] {
|
|
||||||
&.btn-primary {
|
|
||||||
@apply bg-primary hover:bg-primary/90;
|
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
|
||||||
}
|
|
||||||
&.btn-secondary {
|
|
||||||
@apply bg-secondary hover:bg-secondary/80;
|
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
|
||||||
}
|
|
||||||
&.btn-secondary-blue {
|
|
||||||
@apply bg-blue-200 text-blue-900 hover:bg-blue-200/80;
|
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
|
||||||
}
|
|
||||||
&.btn-danger {
|
|
||||||
@apply bg-danger hover:bg-danger/90;
|
|
||||||
@apply disabled:pointer-events-none disabled:bg-zinc-100 disabled:text-zinc-400;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,29 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import * as CheckboxPrimitive from '@radix-ui/react-checkbox'
|
|
||||||
import { CheckIcon } from '@radix-ui/react-icons'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Checkbox = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CheckboxPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CheckboxPrimitive.Root>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CheckboxPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('checkbox', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<CheckboxPrimitive.Indicator
|
|
||||||
className={twMerge(
|
|
||||||
'flex flex-shrink-0 items-center justify-center text-current'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
<CheckIcon className="checkbox--icon" />
|
|
||||||
</CheckboxPrimitive.Indicator>
|
|
||||||
</CheckboxPrimitive.Root>
|
|
||||||
))
|
|
||||||
Checkbox.displayName = CheckboxPrimitive.Root.displayName
|
|
||||||
|
|
||||||
export { Checkbox }
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
.checkbox {
|
|
||||||
@apply border-border data-[state=checked]:bg-primary h-5 w-5 flex-shrink-0 rounded-md border data-[state=checked]:text-white;
|
|
||||||
|
|
||||||
&--icon {
|
|
||||||
@apply h-4 w-4;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,66 +0,0 @@
|
|||||||
/*
|
|
||||||
* react-circular-progressbar styles
|
|
||||||
* All of the styles in this file are configurable!
|
|
||||||
*/
|
|
||||||
|
|
||||||
.CircularProgressbar {
|
|
||||||
/*
|
|
||||||
* This fixes an issue where the CircularProgressbar svg has
|
|
||||||
* 0 width inside a "display: flex" container, and thus not visible.
|
|
||||||
*/
|
|
||||||
width: 100%;
|
|
||||||
/*
|
|
||||||
* This fixes a centering issue with CircularProgressbarWithChildren:
|
|
||||||
* https://github.com/kevinsqi/react-circular-progressbar/issues/94
|
|
||||||
*/
|
|
||||||
vertical-align: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar .CircularProgressbar-path {
|
|
||||||
stroke: #3e98c7;
|
|
||||||
stroke-linecap: round;
|
|
||||||
transition: stroke-dashoffset 0.5s ease 0s;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar .CircularProgressbar-trail {
|
|
||||||
stroke: #d6d6d6;
|
|
||||||
/* Used when trail is not full diameter, i.e. when props.circleRatio is set */
|
|
||||||
stroke-linecap: round;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar .CircularProgressbar-text {
|
|
||||||
fill: #3e98c7;
|
|
||||||
font-size: 20px;
|
|
||||||
dominant-baseline: middle;
|
|
||||||
text-anchor: middle;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar .CircularProgressbar-background {
|
|
||||||
fill: #d6d6d6;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Sample background styles. Use these with e.g.:
|
|
||||||
*
|
|
||||||
* <CircularProgressbar
|
|
||||||
* className="CircularProgressbar-inverted"
|
|
||||||
* background
|
|
||||||
* percentage={50}
|
|
||||||
* />
|
|
||||||
*/
|
|
||||||
.CircularProgressbar.CircularProgressbar-inverted
|
|
||||||
.CircularProgressbar-background {
|
|
||||||
fill: #3e98c7;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-text {
|
|
||||||
fill: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-path {
|
|
||||||
stroke: #fff;
|
|
||||||
}
|
|
||||||
|
|
||||||
.CircularProgressbar.CircularProgressbar-inverted .CircularProgressbar-trail {
|
|
||||||
stroke: transparent;
|
|
||||||
}
|
|
||||||
@ -1,138 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import { DialogProps } from '@radix-ui/react-dialog'
|
|
||||||
import { Command as CommandPrimitive } from 'cmdk'
|
|
||||||
import { Search } from 'lucide-react'
|
|
||||||
|
|
||||||
import { Modal, ModalContent } from '../modal'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Command = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('command', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
Command.displayName = CommandPrimitive.displayName
|
|
||||||
|
|
||||||
interface CommandModalProps extends DialogProps {}
|
|
||||||
|
|
||||||
const CommandModal = ({ children, ...props }: CommandModalProps) => {
|
|
||||||
return (
|
|
||||||
<Modal {...props}>
|
|
||||||
<ModalContent className="command-modal-content">
|
|
||||||
<Command
|
|
||||||
filter={(value, search) => {
|
|
||||||
if (value.includes(search)) return 1
|
|
||||||
return 0
|
|
||||||
}}
|
|
||||||
className="[&_[cmdk-group-heading]]:text-muted-foreground [&_[cmdk-group-heading]]:px-2 [&_[cmdk-group-heading]]:font-medium [&_[cmdk-group]:not([hidden])_~[cmdk-group]]:pt-0 [&_[cmdk-group]]:px-2 [&_[cmdk-input-wrapper]_svg]:h-5 [&_[cmdk-input-wrapper]_svg]:w-5 [&_[cmdk-input]]:h-12 [&_[cmdk-item]]:px-2 [&_[cmdk-item]]:py-3 [&_[cmdk-item]_svg]:h-5 [&_[cmdk-item]_svg]:w-5"
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</Command>
|
|
||||||
</ModalContent>
|
|
||||||
</Modal>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const CommandInput = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Input>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Input>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<div className="command-input-wrapper" cmdk-input-wrapper="">
|
|
||||||
<Search className="command-search-icon" />
|
|
||||||
<CommandPrimitive.Input
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('command-input', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
</div>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandInput.displayName = CommandPrimitive.Input.displayName
|
|
||||||
|
|
||||||
const CommandList = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.List>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.List>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.List
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('command-list', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandList.displayName = CommandPrimitive.List.displayName
|
|
||||||
|
|
||||||
const CommandEmpty = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Empty>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Empty>
|
|
||||||
>((props, ref) => (
|
|
||||||
<CommandPrimitive.Empty ref={ref} className="command-empty" {...props} />
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandEmpty.displayName = CommandPrimitive.Empty.displayName
|
|
||||||
|
|
||||||
const CommandGroup = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Group>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Group>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Group
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('command-group', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandGroup.displayName = CommandPrimitive.Group.displayName
|
|
||||||
|
|
||||||
const CommandSeparator = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Separator>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Separator>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Separator
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('bg-border -mx-1 h-px', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
CommandSeparator.displayName = CommandPrimitive.Separator.displayName
|
|
||||||
|
|
||||||
const CommandItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof CommandPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof CommandPrimitive.Item>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<CommandPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('command-list-item', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
|
|
||||||
CommandItem.displayName = CommandPrimitive.Item.displayName
|
|
||||||
|
|
||||||
const CommandShortcut = ({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLSpanElement>) => {
|
|
||||||
return <span className={twMerge('command-sc', className)} {...props} />
|
|
||||||
}
|
|
||||||
CommandShortcut.displayName = 'CommandShortcut'
|
|
||||||
|
|
||||||
export {
|
|
||||||
Command,
|
|
||||||
CommandModal,
|
|
||||||
CommandInput,
|
|
||||||
CommandList,
|
|
||||||
CommandEmpty,
|
|
||||||
CommandGroup,
|
|
||||||
CommandItem,
|
|
||||||
CommandShortcut,
|
|
||||||
CommandSeparator,
|
|
||||||
}
|
|
||||||
@ -1,45 +0,0 @@
|
|||||||
.command {
|
|
||||||
@apply bg-background/80 text-muted-foreground flex h-full w-full flex-col overflow-hidden rounded-md text-left;
|
|
||||||
|
|
||||||
&-modal-content {
|
|
||||||
@apply overflow-hidden p-0;
|
|
||||||
> .modal-close {
|
|
||||||
top: 12px;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input-wrapper {
|
|
||||||
@apply border-border flex items-center border-b px-3;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input {
|
|
||||||
@apply placeholder:text-muted-foreground flex h-11 w-full rounded-md bg-transparent py-3 text-sm outline-none disabled:cursor-not-allowed disabled:opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-search-icon {
|
|
||||||
@apply mr-2 h-4 w-4 shrink-0 opacity-50;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list {
|
|
||||||
@apply max-h-[300px] overflow-y-auto overflow-x-hidden py-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-list-item {
|
|
||||||
@apply text-foreground aria-selected:bg-secondary relative flex cursor-pointer select-none items-center rounded-md px-2 py-2 text-sm outline-none;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-empty {
|
|
||||||
@apply py-6 text-center text-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-group {
|
|
||||||
@apply text-muted-foreground overflow-hidden p-1 px-2 py-1.5 text-xs font-medium;
|
|
||||||
> [cmdk-group-heading] {
|
|
||||||
@apply mb-2 pl-2;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-sc {
|
|
||||||
@apply text-muted-foreground ml-auto text-xs tracking-widest;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,175 +0,0 @@
|
|||||||
import * as React from 'react'
|
|
||||||
import * as LabelPrimitive from '@radix-ui/react-label'
|
|
||||||
import { Slot } from '@radix-ui/react-slot'
|
|
||||||
import {
|
|
||||||
Controller,
|
|
||||||
ControllerProps,
|
|
||||||
FieldPath,
|
|
||||||
FieldValues,
|
|
||||||
FormProvider,
|
|
||||||
useFormContext,
|
|
||||||
} from 'react-hook-form'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Form = FormProvider
|
|
||||||
|
|
||||||
type FormFieldContextValue<
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
> = {
|
|
||||||
name: TName
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormFieldContext = React.createContext<FormFieldContextValue>(
|
|
||||||
{} as FormFieldContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormField = <
|
|
||||||
TFieldValues extends FieldValues = FieldValues,
|
|
||||||
TName extends FieldPath<TFieldValues> = FieldPath<TFieldValues>,
|
|
||||||
>({
|
|
||||||
...props
|
|
||||||
}: ControllerProps<TFieldValues, TName>) => {
|
|
||||||
return (
|
|
||||||
<FormFieldContext.Provider value={{ name: props.name }}>
|
|
||||||
<Controller {...props} />
|
|
||||||
</FormFieldContext.Provider>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
const useFormField = () => {
|
|
||||||
const fieldContext = React.useContext(FormFieldContext)
|
|
||||||
const itemContext = React.useContext(FormItemContext)
|
|
||||||
const { getFieldState, formState } = useFormContext()
|
|
||||||
|
|
||||||
const fieldState = getFieldState(fieldContext.name, formState)
|
|
||||||
|
|
||||||
if (!fieldContext) {
|
|
||||||
throw new Error('useFormField should be used within <FormField>')
|
|
||||||
}
|
|
||||||
|
|
||||||
const { id } = itemContext
|
|
||||||
|
|
||||||
return {
|
|
||||||
id,
|
|
||||||
name: fieldContext.name,
|
|
||||||
formItemId: `${id}-form-item`,
|
|
||||||
formDescriptionId: `${id}-form-item-description`,
|
|
||||||
formMessageId: `${id}-form-item-message`,
|
|
||||||
...fieldState,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
type FormItemContextValue = {
|
|
||||||
id: string
|
|
||||||
}
|
|
||||||
|
|
||||||
const FormItemContext = React.createContext<FormItemContextValue>(
|
|
||||||
{} as FormItemContextValue
|
|
||||||
)
|
|
||||||
|
|
||||||
const FormItem = React.forwardRef<
|
|
||||||
HTMLDivElement,
|
|
||||||
React.HTMLAttributes<HTMLDivElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const id = React.useId()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<FormItemContext.Provider value={{ id }}>
|
|
||||||
<div ref={ref} className={twMerge(className)} {...props} />
|
|
||||||
</FormItemContext.Provider>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
FormItem.displayName = 'FormItem'
|
|
||||||
|
|
||||||
const FormLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof LabelPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof LabelPrimitive.Root>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { error, formItemId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<label
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('form-label', className)}
|
|
||||||
htmlFor={formItemId}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
FormLabel.displayName = 'FormLabel'
|
|
||||||
|
|
||||||
const FormControl = React.forwardRef<
|
|
||||||
React.ElementRef<typeof Slot>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof Slot>
|
|
||||||
>(({ ...props }, ref) => {
|
|
||||||
const { error, formItemId, formDescriptionId, formMessageId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<Slot
|
|
||||||
ref={ref}
|
|
||||||
id={formItemId}
|
|
||||||
className={error && 'form-input-error'}
|
|
||||||
aria-describedby={
|
|
||||||
!error
|
|
||||||
? `${formDescriptionId}`
|
|
||||||
: `${formDescriptionId} ${formMessageId}`
|
|
||||||
}
|
|
||||||
aria-invalid={!!error}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
FormControl.displayName = 'FormControl'
|
|
||||||
|
|
||||||
const FormDescription = React.forwardRef<
|
|
||||||
HTMLParagraphElement,
|
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, ...props }, ref) => {
|
|
||||||
const { formDescriptionId } = useFormField()
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
id={formDescriptionId}
|
|
||||||
className={twMerge('form-description', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
FormDescription.displayName = 'FormDescription'
|
|
||||||
|
|
||||||
const FormMessage = React.forwardRef<
|
|
||||||
HTMLParagraphElement,
|
|
||||||
React.HTMLAttributes<HTMLParagraphElement>
|
|
||||||
>(({ className, children, ...props }, ref) => {
|
|
||||||
const { error, formMessageId } = useFormField()
|
|
||||||
const body = error ? String(error?.message) : children
|
|
||||||
|
|
||||||
if (!body) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
ref={ref}
|
|
||||||
id={formMessageId}
|
|
||||||
className={twMerge('form-message', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{body}
|
|
||||||
</p>
|
|
||||||
)
|
|
||||||
})
|
|
||||||
FormMessage.displayName = 'FormMessage'
|
|
||||||
|
|
||||||
export {
|
|
||||||
useFormField,
|
|
||||||
Form,
|
|
||||||
FormItem,
|
|
||||||
FormLabel,
|
|
||||||
FormControl,
|
|
||||||
FormDescription,
|
|
||||||
FormMessage,
|
|
||||||
FormField,
|
|
||||||
}
|
|
||||||
@ -1,21 +0,0 @@
|
|||||||
.form {
|
|
||||||
&-item {
|
|
||||||
@apply space-y-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-input-error {
|
|
||||||
@apply border-danger;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-label {
|
|
||||||
@apply mb-2 inline-block cursor-pointer font-medium;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-description {
|
|
||||||
@apply text-muted-foreground text-xs;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-message {
|
|
||||||
@apply text-danger mt-2 text-xs font-medium;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,15 +0,0 @@
|
|||||||
export * from './avatar'
|
|
||||||
export * from './switch'
|
|
||||||
export * from './button'
|
|
||||||
export * from './scroll-area'
|
|
||||||
export * from './form'
|
|
||||||
export * from './input'
|
|
||||||
export * from './progress'
|
|
||||||
export * from './badge'
|
|
||||||
export * from './tooltip'
|
|
||||||
export * from './modal'
|
|
||||||
export * from './command'
|
|
||||||
export * from './textarea'
|
|
||||||
export * from './select'
|
|
||||||
export * from './slider'
|
|
||||||
export * from './checkbox'
|
|
||||||
@ -1,27 +0,0 @@
|
|||||||
import { forwardRef } from 'react'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
export interface InputProps
|
|
||||||
extends React.InputHTMLAttributes<HTMLInputElement> {
|
|
||||||
textAlign?: 'left' | 'right'
|
|
||||||
}
|
|
||||||
|
|
||||||
const Input = forwardRef<HTMLInputElement, InputProps>(
|
|
||||||
({ className, type, textAlign, ...props }, ref) => {
|
|
||||||
return (
|
|
||||||
<input
|
|
||||||
type={type}
|
|
||||||
className={twMerge(
|
|
||||||
'input',
|
|
||||||
className,
|
|
||||||
textAlign === 'right' && 'text-right'
|
|
||||||
)}
|
|
||||||
ref={ref}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
Input.displayName = 'Input'
|
|
||||||
|
|
||||||
export { Input }
|
|
||||||
@ -1,9 +0,0 @@
|
|||||||
.input {
|
|
||||||
@apply border-border placeholder:text-muted-foreground flex h-9 w-full rounded-lg border bg-transparent px-3 py-1 transition-colors;
|
|
||||||
@apply disabled:text-muted-foreground disabled:cursor-not-allowed disabled:bg-zinc-100 disabled:dark:bg-zinc-800 disabled:dark:text-zinc-600;
|
|
||||||
@apply focus-within:outline-none focus-visible:outline-0 focus-visible:ring-2 focus-visible:ring-blue-500 focus-visible:ring-offset-1;
|
|
||||||
@apply file:border-0 file:bg-transparent file:font-medium;
|
|
||||||
&.text-right {
|
|
||||||
text-align: right;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,111 +0,0 @@
|
|||||||
@import 'tailwindcss/base';
|
|
||||||
@import 'tailwindcss/components';
|
|
||||||
@import 'tailwindcss/utilities';
|
|
||||||
|
|
||||||
@import './avatar/styles.scss';
|
|
||||||
@import './switch/styles.scss';
|
|
||||||
@import './button/styles.scss';
|
|
||||||
@import './scroll-area/styles.scss';
|
|
||||||
@import './form/styles.scss';
|
|
||||||
@import './input/styles.scss';
|
|
||||||
@import './progress/styles.scss';
|
|
||||||
@import './badge/styles.scss';
|
|
||||||
@import './tooltip/styles.scss';
|
|
||||||
@import './modal/styles.scss';
|
|
||||||
@import './command/styles.scss';
|
|
||||||
@import './textarea/styles.scss';
|
|
||||||
@import './select/styles.scss';
|
|
||||||
@import './slider/styles.scss';
|
|
||||||
@import './checkbox/styles.scss';
|
|
||||||
@import './circular-progress/styles.scss';
|
|
||||||
|
|
||||||
.animate-spin {
|
|
||||||
animation: spin 1s linear infinite;
|
|
||||||
}
|
|
||||||
|
|
||||||
@keyframes spin {
|
|
||||||
from {
|
|
||||||
transform: rotate(0deg);
|
|
||||||
}
|
|
||||||
to {
|
|
||||||
transform: rotate(360deg);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
:root {
|
|
||||||
--background: 0 0% 100%;
|
|
||||||
--foreground: 20 14.3% 4.1%;
|
|
||||||
|
|
||||||
--muted: 60 4.8% 95.9%;
|
|
||||||
--muted-foreground: 240 3.8% 46.1%;
|
|
||||||
|
|
||||||
--danger: 346.8 77.2% 49.8%;
|
|
||||||
--danger-foreground: 355.7 100% 97.3%;
|
|
||||||
|
|
||||||
--border: 20 5.9% 90%;
|
|
||||||
--input: 20 5.9% 90%;
|
|
||||||
--ring: 20 14.3% 4.1%;
|
|
||||||
--scroll-bar: 60, 3%, 86%;
|
|
||||||
|
|
||||||
.primary-blue {
|
|
||||||
--primary: 221 83% 53%;
|
|
||||||
--primary-foreground: 210 40% 98%;
|
|
||||||
|
|
||||||
--secondary: 60 4.8% 95.9%;
|
|
||||||
--secondary-foreground: 24 9.8% 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-green {
|
|
||||||
--primary: 142.1 76.2% 36.3%;
|
|
||||||
--primary-foreground: 355.7 100% 97.3%;
|
|
||||||
|
|
||||||
--secondary: 240 4.8% 95.9%;
|
|
||||||
--secondary-foreground: 240 5.9% 10%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-purple {
|
|
||||||
--primary: 262.1 83.3% 57.8%;
|
|
||||||
--primary-foreground: 210 20% 98%;
|
|
||||||
|
|
||||||
--secondary: 220 14.3% 95.9%;
|
|
||||||
--secondary-foreground: 220.9 39.3% 11%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.dark {
|
|
||||||
--background: 20 14.3% 4.1%;
|
|
||||||
--foreground: 60 9.1% 97.8%;
|
|
||||||
|
|
||||||
--muted: 12 6.5% 15.1%;
|
|
||||||
--muted-foreground: 24 5.4% 63.9%;
|
|
||||||
|
|
||||||
--danger: 346.8 77.2% 49.8%;
|
|
||||||
--danger-foreground: 355.7 100% 97.3%;
|
|
||||||
|
|
||||||
--border: 12 6.5% 15.1%;
|
|
||||||
--input: 12 6.5% 15.1%;
|
|
||||||
--ring: 35.5 91.7% 32.9%;
|
|
||||||
|
|
||||||
.primary-blue {
|
|
||||||
--primary: 221 83% 53%;
|
|
||||||
--primary-foreground: 222.2 47.4% 11.2%;
|
|
||||||
|
|
||||||
--secondary: 12 6.5% 15.1%;
|
|
||||||
--secondary-foreground: 60 9.1% 97.8%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-green {
|
|
||||||
--primary: 142.1 70.6% 45.3%;
|
|
||||||
--primary-foreground: 144.9 80.4% 10%;
|
|
||||||
--secondary: 240 3.7% 15.9%;
|
|
||||||
--secondary-foreground: 0 0% 98%;
|
|
||||||
}
|
|
||||||
|
|
||||||
.primary-purple {
|
|
||||||
--primary: 263.4 70% 50.4%;
|
|
||||||
--primary-foreground: 210 20% 98%;
|
|
||||||
|
|
||||||
--secondary: 215 27.9% 16.9%;
|
|
||||||
--secondary-foreground: 210 20% 98%;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,99 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import * as ModalPrimitive from '@radix-ui/react-dialog'
|
|
||||||
import { X } from 'lucide-react'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Modal = ModalPrimitive.Root
|
|
||||||
|
|
||||||
const ModalTrigger = ModalPrimitive.Trigger
|
|
||||||
|
|
||||||
const ModalPortal = ModalPrimitive.Portal
|
|
||||||
|
|
||||||
const ModalClose = ModalPrimitive.Close
|
|
||||||
|
|
||||||
const ModalOverlay = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ModalPrimitive.Overlay>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ModalPrimitive.Overlay>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ModalPrimitive.Overlay
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('modal-backdrop', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ModalOverlay.displayName = ModalPrimitive.Overlay.displayName
|
|
||||||
|
|
||||||
const ModalContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ModalPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ModalPrimitive.Content>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<ModalPortal>
|
|
||||||
<ModalOverlay />
|
|
||||||
<ModalPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('modal-content', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<ModalPrimitive.Close className="modal-close">
|
|
||||||
<X size={20} />
|
|
||||||
</ModalPrimitive.Close>
|
|
||||||
</ModalPrimitive.Content>
|
|
||||||
</ModalPortal>
|
|
||||||
))
|
|
||||||
ModalContent.displayName = ModalPrimitive.Content.displayName
|
|
||||||
|
|
||||||
const ModalHeader = ({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
||||||
<div className={twMerge('modal-header', className)} {...props} />
|
|
||||||
)
|
|
||||||
ModalHeader.displayName = 'ModalHeader'
|
|
||||||
|
|
||||||
const ModalFooter = ({
|
|
||||||
className,
|
|
||||||
...props
|
|
||||||
}: React.HTMLAttributes<HTMLDivElement>) => (
|
|
||||||
<div className={twMerge('modal-footer', className)} {...props} />
|
|
||||||
)
|
|
||||||
ModalFooter.displayName = 'ModalFooter'
|
|
||||||
|
|
||||||
const ModalTitle = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ModalPrimitive.Title>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ModalPrimitive.Title>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ModalPrimitive.Title
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('modal-title', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ModalTitle.displayName = ModalPrimitive.Title.displayName
|
|
||||||
|
|
||||||
const ModalDescription = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ModalPrimitive.Description>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ModalPrimitive.Description>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<ModalPrimitive.Description
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('modal-description', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
ModalDescription.displayName = ModalPrimitive.Description.displayName
|
|
||||||
|
|
||||||
export {
|
|
||||||
Modal,
|
|
||||||
ModalPortal,
|
|
||||||
ModalOverlay,
|
|
||||||
ModalClose,
|
|
||||||
ModalTrigger,
|
|
||||||
ModalContent,
|
|
||||||
ModalHeader,
|
|
||||||
ModalFooter,
|
|
||||||
ModalTitle,
|
|
||||||
ModalDescription,
|
|
||||||
}
|
|
||||||
@ -1,32 +0,0 @@
|
|||||||
.modal {
|
|
||||||
&-backdrop {
|
|
||||||
@apply bg-background/80 fixed inset-0 z-50 backdrop-blur-sm;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-content {
|
|
||||||
@apply bg-background border-border fixed left-[50%] top-[50%] z-50 grid max-h-[calc(100%-48px)] w-full max-w-lg translate-x-[-50%] translate-y-[-50%] gap-4 overflow-y-auto border p-4 shadow-lg duration-200 sm:rounded-lg md:w-full;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-close {
|
|
||||||
@apply absolute right-4 top-4 rounded-sm opacity-70 transition-opacity hover:opacity-100 focus:outline-none disabled:pointer-events-none;
|
|
||||||
> svg {
|
|
||||||
@apply text-muted-foreground;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
&-header {
|
|
||||||
@apply flex flex-col space-y-1.5 text-center sm:text-left;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-footer {
|
|
||||||
@apply flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-title {
|
|
||||||
@apply text-lg font-semibold leading-none tracking-tight;
|
|
||||||
}
|
|
||||||
|
|
||||||
&-description {
|
|
||||||
@apply text-muted-foreground text-sm;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,24 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import * as ProgressPrimitive from '@radix-ui/react-progress'
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Progress = React.forwardRef<
|
|
||||||
React.ElementRef<typeof ProgressPrimitive.Root>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof ProgressPrimitive.Root>
|
|
||||||
>(({ className, value, ...props }, ref) => (
|
|
||||||
<ProgressPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('progress', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ProgressPrimitive.Indicator
|
|
||||||
className="progress-indicator"
|
|
||||||
style={{ transform: `translateX(-${100 - (value || 0)}%)` }}
|
|
||||||
/>
|
|
||||||
</ProgressPrimitive.Root>
|
|
||||||
))
|
|
||||||
Progress.displayName = ProgressPrimitive.Root.displayName
|
|
||||||
|
|
||||||
export { Progress }
|
|
||||||
@ -1,7 +0,0 @@
|
|||||||
.progress {
|
|
||||||
@apply bg-secondary relative h-4 w-full overflow-hidden rounded-full;
|
|
||||||
|
|
||||||
&-indicator {
|
|
||||||
@apply bg-primary h-full w-full flex-1 transition-all;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -1,51 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import { forwardRef, ElementRef, ComponentPropsWithoutRef } from 'react'
|
|
||||||
import * as ScrollAreaPrimitive from '@radix-ui/react-scroll-area'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const ScrollArea = forwardRef<
|
|
||||||
ElementRef<typeof ScrollAreaPrimitive.Root>,
|
|
||||||
ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.Root>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<ScrollAreaPrimitive.Root
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('scroll-area', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.Viewport className="scroll-area-viewport">
|
|
||||||
{children}
|
|
||||||
</ScrollAreaPrimitive.Viewport>
|
|
||||||
<ScrollBar />
|
|
||||||
<ScrollAreaPrimitive.Corner />
|
|
||||||
</ScrollAreaPrimitive.Root>
|
|
||||||
))
|
|
||||||
ScrollArea.displayName = ScrollAreaPrimitive.Root.displayName
|
|
||||||
|
|
||||||
const ScrollBar = forwardRef<
|
|
||||||
ElementRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>,
|
|
||||||
ComponentPropsWithoutRef<typeof ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
||||||
>(({ className, orientation = 'vertical', ...props }, ref) => (
|
|
||||||
<ScrollAreaPrimitive.ScrollAreaScrollbar
|
|
||||||
ref={ref}
|
|
||||||
orientation={orientation}
|
|
||||||
className={twMerge(
|
|
||||||
'scroll-bar',
|
|
||||||
orientation === 'vertical' && 'scroll-bar-vertical',
|
|
||||||
orientation === 'horizontal' && 'scroll-bar-vertical ',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ScrollAreaPrimitive.ScrollAreaThumb
|
|
||||||
className={twMerge(
|
|
||||||
'scroll-bar-thumb',
|
|
||||||
orientation === 'vertical' && 'flex-1'
|
|
||||||
)}
|
|
||||||
/>
|
|
||||||
</ScrollAreaPrimitive.ScrollAreaScrollbar>
|
|
||||||
))
|
|
||||||
ScrollBar.displayName = ScrollAreaPrimitive.ScrollAreaScrollbar.displayName
|
|
||||||
|
|
||||||
export { ScrollArea, ScrollBar }
|
|
||||||
@ -1,58 +0,0 @@
|
|||||||
.scroll-area {
|
|
||||||
@apply relative overflow-hidden;
|
|
||||||
|
|
||||||
&-viewport {
|
|
||||||
@apply h-full w-full rounded-[inherit];
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
.scroll-bar {
|
|
||||||
@apply flex touch-none select-none transition-colors;
|
|
||||||
|
|
||||||
&-vertical {
|
|
||||||
@apply h-full w-2.5 border-l border-l-transparent p-[1px];
|
|
||||||
}
|
|
||||||
|
|
||||||
&-horizontal {
|
|
||||||
@apply h-2.5 flex-col border-t border-t-transparent p-[1px];
|
|
||||||
}
|
|
||||||
|
|
||||||
&-thumb {
|
|
||||||
@apply bg-border relative z-50 w-[10px] rounded-full;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Customized scroll bar
|
|
||||||
::-webkit-scrollbar {
|
|
||||||
width: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-thumb {
|
|
||||||
background-color: hsl(var(--scroll-bar));
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-track {
|
|
||||||
background-color: hsl(var(--background));
|
|
||||||
}
|
|
||||||
|
|
||||||
::-webkit-scrollbar-corner {
|
|
||||||
background-color: hsl(var(--background));
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-scrollbar {
|
|
||||||
width: 7px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-scrollbar-thumb {
|
|
||||||
background-color: hsl(var(--scroll-bar));
|
|
||||||
border-radius: 4px;
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-scrollbar-track {
|
|
||||||
background-color: hsl(var(--background));
|
|
||||||
}
|
|
||||||
|
|
||||||
::-moz-scrollbar-corner {
|
|
||||||
background-color: hsl(var(--background));
|
|
||||||
}
|
|
||||||
@ -1,136 +0,0 @@
|
|||||||
'use client'
|
|
||||||
|
|
||||||
import * as React from 'react'
|
|
||||||
import {
|
|
||||||
CaretSortIcon,
|
|
||||||
ChevronDownIcon,
|
|
||||||
ChevronUpIcon,
|
|
||||||
} from '@radix-ui/react-icons'
|
|
||||||
|
|
||||||
import * as SelectPrimitive from '@radix-ui/react-select'
|
|
||||||
|
|
||||||
import { twMerge } from 'tailwind-merge'
|
|
||||||
|
|
||||||
const Select = SelectPrimitive.Root
|
|
||||||
|
|
||||||
const SelectGroup = SelectPrimitive.Group
|
|
||||||
|
|
||||||
const SelectValue = SelectPrimitive.Value
|
|
||||||
|
|
||||||
const SelectPortal = SelectPrimitive.Portal
|
|
||||||
|
|
||||||
const SelectTrigger = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.Trigger>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Trigger>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<SelectPrimitive.Trigger
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('select', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
<SelectPrimitive.Icon asChild>
|
|
||||||
<CaretSortIcon className="select-caret" />
|
|
||||||
</SelectPrimitive.Icon>
|
|
||||||
</SelectPrimitive.Trigger>
|
|
||||||
))
|
|
||||||
SelectTrigger.displayName = SelectPrimitive.Trigger.displayName
|
|
||||||
|
|
||||||
const SelectScrollUpButton = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.ScrollUpButton>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollUpButton>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<SelectPrimitive.ScrollUpButton
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('select-scroll-up-button', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ChevronUpIcon />
|
|
||||||
</SelectPrimitive.ScrollUpButton>
|
|
||||||
))
|
|
||||||
SelectScrollUpButton.displayName = SelectPrimitive.ScrollUpButton.displayName
|
|
||||||
|
|
||||||
const SelectScrollDownButton = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.ScrollDownButton>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.ScrollDownButton>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<SelectPrimitive.ScrollDownButton
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('select-scroll-down-button', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<ChevronDownIcon />
|
|
||||||
</SelectPrimitive.ScrollDownButton>
|
|
||||||
))
|
|
||||||
SelectScrollDownButton.displayName =
|
|
||||||
SelectPrimitive.ScrollDownButton.displayName
|
|
||||||
|
|
||||||
const SelectContent = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.Content>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Content>
|
|
||||||
>(({ className, children, position = 'popper', ...props }, ref) => (
|
|
||||||
<SelectPrimitive.Portal>
|
|
||||||
<SelectPrimitive.Content
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge(
|
|
||||||
'select-content',
|
|
||||||
position === 'popper' &&
|
|
||||||
'data-[side=bottom]:translate-y-1 data-[side=left]:-translate-x-1 data-[side=right]:translate-x-1 data-[side=top]:-translate-y-1',
|
|
||||||
className
|
|
||||||
)}
|
|
||||||
position={position}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SelectScrollUpButton />
|
|
||||||
<SelectPrimitive.Viewport
|
|
||||||
className={twMerge(
|
|
||||||
'select-trigger-viewport',
|
|
||||||
position === 'popper' && 'w-full'
|
|
||||||
)}
|
|
||||||
>
|
|
||||||
{children}
|
|
||||||
</SelectPrimitive.Viewport>
|
|
||||||
<SelectScrollDownButton />
|
|
||||||
</SelectPrimitive.Content>
|
|
||||||
</SelectPrimitive.Portal>
|
|
||||||
))
|
|
||||||
SelectContent.displayName = SelectPrimitive.Content.displayName
|
|
||||||
|
|
||||||
const SelectLabel = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.Label>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Label>
|
|
||||||
>(({ className, ...props }, ref) => (
|
|
||||||
<SelectPrimitive.Label
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('select-label', className)}
|
|
||||||
{...props}
|
|
||||||
/>
|
|
||||||
))
|
|
||||||
SelectLabel.displayName = SelectPrimitive.Label.displayName
|
|
||||||
|
|
||||||
const SelectItem = React.forwardRef<
|
|
||||||
React.ElementRef<typeof SelectPrimitive.Item>,
|
|
||||||
React.ComponentPropsWithoutRef<typeof SelectPrimitive.Item>
|
|
||||||
>(({ className, children, ...props }, ref) => (
|
|
||||||
<SelectPrimitive.Item
|
|
||||||
ref={ref}
|
|
||||||
className={twMerge('select-item', className)}
|
|
||||||
{...props}
|
|
||||||
>
|
|
||||||
<SelectPrimitive.ItemText>{children}</SelectPrimitive.ItemText>
|
|
||||||
</SelectPrimitive.Item>
|
|
||||||
))
|
|
||||||
SelectItem.displayName = SelectPrimitive.Item.displayName
|
|
||||||
|
|
||||||
export {
|
|
||||||
Select,
|
|
||||||
SelectGroup,
|
|
||||||
SelectValue,
|
|
||||||
SelectPortal,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectContent,
|
|
||||||
SelectLabel,
|
|
||||||
SelectItem,
|
|
||||||
SelectScrollUpButton,
|
|
||||||
SelectScrollDownButton,
|
|
||||||
}
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user