Compare commits
2 Commits
prep-ui-ve
...
pr-3785
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4035c25b3d | ||
|
|
23c1ae031f |
@@ -1,75 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Shared script for creating pull requests in Gitea workflows
|
||||
set -euo pipefail
|
||||
|
||||
# Required environment variables:
|
||||
# - CI_BOT_TOKEN: Gitea bot token for authentication
|
||||
# - PR_BRANCH: Branch name for the pull request
|
||||
# - PR_TITLE: Title of the pull request
|
||||
# - PR_BODY: Body/description of the pull request
|
||||
|
||||
if [[ -z "${CI_BOT_TOKEN:-}" ]]; then
|
||||
echo "Error: CI_BOT_TOKEN is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_BRANCH:-}" ]]; then
|
||||
echo "Error: PR_BRANCH is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_TITLE:-}" ]]; then
|
||||
echo "Error: PR_TITLE is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -z "${PR_BODY:-}" ]]; then
|
||||
echo "Error: PR_BODY is not set" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Push the branch
|
||||
git push origin "+HEAD:${PR_BRANCH}"
|
||||
|
||||
# Create pull request
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d "{
|
||||
\"head\": \"${PR_BRANCH}\",
|
||||
\"base\": \"main\",
|
||||
\"title\": \"${PR_TITLE}\",
|
||||
\"body\": \"${PR_BODY}\"
|
||||
}" \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
|
||||
|
||||
pr_number=$(echo "$resp" | jq -r '.number')
|
||||
|
||||
if [[ "$pr_number" == "null" ]]; then
|
||||
echo "Error creating pull request:" >&2
|
||||
echo "$resp" | jq . >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
echo "Created pull request #$pr_number"
|
||||
|
||||
# Merge when checks succeed
|
||||
while true; do
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"Do": "merge",
|
||||
"merge_when_checks_succeed": true,
|
||||
"delete_branch_after_merge": true
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
|
||||
msg=$(echo "$resp" | jq -r '.message')
|
||||
if [[ "$msg" != "Please try again later" ]]; then
|
||||
break
|
||||
fi
|
||||
echo "Retrying in 2 seconds..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
echo "Pull request #$pr_number merge initiated"
|
||||
@@ -19,10 +19,35 @@ jobs:
|
||||
run: |
|
||||
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
|
||||
git commit -am "Update pinned clan-core for checks"
|
||||
git push origin +HEAD:update-clan-core-for-checks
|
||||
set -x
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"head": "update-clan-core-for-checks",
|
||||
"base": "main",
|
||||
"title": "Update Clan Core for Checks",
|
||||
"body": "This PR updates the pinned clan-core flake input that is used for checks."
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls")
|
||||
pr_number=$(echo "$resp" | jq -r '.number')
|
||||
|
||||
# Use shared PR creation script
|
||||
export PR_BRANCH="update-clan-core-for-checks"
|
||||
export PR_TITLE="Update Clan Core for Checks"
|
||||
export PR_BODY="This PR updates the pinned clan-core flake input that is used for checks."
|
||||
|
||||
./.gitea/workflows/create-pr.sh
|
||||
# Merge when succeed
|
||||
while true; do
|
||||
resp=$(nix run --inputs-from . nixpkgs#curl -- -X POST \
|
||||
-H "Authorization: token $CI_BOT_TOKEN" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{
|
||||
"Do": "merge",
|
||||
"merge_when_checks_succeed": true,
|
||||
"delete_branch_after_merge": true
|
||||
}' \
|
||||
"https://git.clan.lol/api/v1/repos/clan/clan-core/pulls/$pr_number/merge")
|
||||
msg=$(echo $resp | jq -r '.message')
|
||||
if [[ "$msg" != "Please try again later" ]]; then
|
||||
break
|
||||
fi
|
||||
echo "Retrying in 2 seconds..."
|
||||
sleep 2
|
||||
done
|
||||
|
||||
@@ -1,40 +0,0 @@
|
||||
name: "Update private flake inputs"
|
||||
on:
|
||||
repository_dispatch:
|
||||
workflow_dispatch:
|
||||
schedule:
|
||||
- cron: "0 3 * * *" # Run daily at 3 AM
|
||||
jobs:
|
||||
update-private-flake:
|
||||
runs-on: nix
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
submodules: true
|
||||
- name: Update private flake inputs
|
||||
run: |
|
||||
# Update the private flake lock file
|
||||
cd devFlake/private
|
||||
nix flake update
|
||||
cd ../..
|
||||
|
||||
# Update the narHash
|
||||
bash ./devFlake/update-private-narhash
|
||||
- name: Create pull request
|
||||
env:
|
||||
CI_BOT_TOKEN: ${{ secrets.CI_BOT_TOKEN }}
|
||||
run: |
|
||||
export GIT_AUTHOR_NAME=clan-bot GIT_AUTHOR_EMAIL=clan-bot@clan.lol GIT_COMMITTER_NAME=clan-bot GIT_COMMITTER_EMAIL=clan-bot@clan.lol
|
||||
|
||||
# Check if there are any changes
|
||||
if ! git diff --quiet; then
|
||||
git add devFlake/private/flake.lock devFlake/private.narHash
|
||||
git commit -m "Update dev flake"
|
||||
|
||||
# Use shared PR creation script
|
||||
export PR_BRANCH="update-dev-flake"
|
||||
export PR_TITLE="Update dev flake"
|
||||
export PR_BODY="This PR updates the dev flake inputs and corresponding narHash."
|
||||
else
|
||||
echo "No changes detected in dev flake inputs"
|
||||
fi
|
||||
@@ -19,11 +19,10 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies =
|
||||
[
|
||||
pkgs.stdenv.drvPath
|
||||
]
|
||||
++ builtins.map (i: i.outPath) (builtins.attrValues (builtins.removeAttrs self.inputs [ "self" ]));
|
||||
dependencies = [
|
||||
self
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues self.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
in
|
||||
{
|
||||
|
||||
@@ -47,6 +47,14 @@ nixosLib.runTest (
|
||||
|
||||
clientone =
|
||||
{ config, pkgs, ... }:
|
||||
let
|
||||
dependencies = [
|
||||
clan-core
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
services.openssh.enable = true;
|
||||
@@ -57,6 +65,15 @@ nixosLib.runTest (
|
||||
|
||||
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
|
||||
|
||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||
nix.settings = {
|
||||
substituters = pkgs.lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = pkgs.lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
|
||||
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
||||
};
|
||||
|
||||
|
||||
@@ -41,6 +41,14 @@
|
||||
clan-core,
|
||||
...
|
||||
}:
|
||||
let
|
||||
dependencies = [
|
||||
clan-core
|
||||
pkgs.stdenv.drvPath
|
||||
] ++ builtins.map (i: i.outPath) (builtins.attrValues clan-core.inputs);
|
||||
closureInfo = pkgs.closureInfo { rootPaths = dependencies; };
|
||||
|
||||
in
|
||||
{
|
||||
|
||||
services.openssh.enable = true;
|
||||
@@ -51,6 +59,15 @@
|
||||
|
||||
environment.systemPackages = [ clan-core.packages.${pkgs.system}.clan-cli ];
|
||||
|
||||
environment.etc.install-closure.source = "${closureInfo}/store-paths";
|
||||
nix.settings = {
|
||||
substituters = pkgs.lib.mkForce [ ];
|
||||
hashed-mirrors = null;
|
||||
connect-timeout = pkgs.lib.mkForce 3;
|
||||
flake-registry = pkgs.writeText "flake-registry" ''{"flakes":[],"version":2}'';
|
||||
};
|
||||
system.extraDependencies = dependencies;
|
||||
|
||||
clan.core.state.test-backups.folders = [ "/var/test-backups" ];
|
||||
};
|
||||
|
||||
|
||||
@@ -23,13 +23,7 @@ in
|
||||
unit-test-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
inherit module;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# The hello-world service being tested
|
||||
../../clanServices/hello-world
|
||||
# Required modules
|
||||
../../nixosModules/clanCore
|
||||
];
|
||||
inherit self inputs;
|
||||
testName = "hello-world";
|
||||
tests = ./tests/eval-tests.nix;
|
||||
# Optional arguments passed to the test
|
||||
|
||||
@@ -15,15 +15,7 @@ in
|
||||
unit-test-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
inherit module;
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# The zerotier service being tested
|
||||
../../clanServices/zerotier
|
||||
# Required modules
|
||||
../../nixosModules/clanCore
|
||||
# Dependencies like clan-cli
|
||||
../../pkgs/clan-cli
|
||||
];
|
||||
inherit self inputs;
|
||||
testName = "zerotier";
|
||||
tests = ./tests/eval-tests.nix;
|
||||
testArgs = { };
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
sha256-pFUj3KhQ4FkzZT19t+FHBru8u8Lspax0rS2cv7nXIgM=
|
||||
165
devFlake/private/flake.lock
generated
165
devFlake/private/flake.lock
generated
@@ -1,165 +0,0 @@
|
||||
{
|
||||
"nodes": {
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils_2": {
|
||||
"inputs": {
|
||||
"systems": "systems"
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ixx": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"nuschtos",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nuschtos",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748294338,
|
||||
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "ixx",
|
||||
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"ref": "v0.0.8",
|
||||
"repo": "ixx",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nixpkgs-dev": {
|
||||
"locked": {
|
||||
"lastModified": 1751867001,
|
||||
"narHash": "sha256-3I49W0s3WVEDBO5S1RxYr74E2LLG7X8Wuvj9AmU0RDk=",
|
||||
"owner": "NixOS",
|
||||
"repo": "nixpkgs",
|
||||
"rev": "73feb5e20ec7259e280ca6f424ba165059b3bb6b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NixOS",
|
||||
"ref": "nixos-unstable-small",
|
||||
"repo": "nixpkgs",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nuschtos": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils_2",
|
||||
"ixx": "ixx",
|
||||
"nixpkgs": [
|
||||
"nixpkgs-dev"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749730855,
|
||||
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"flake-utils": "flake-utils",
|
||||
"nixpkgs-dev": "nixpkgs-dev",
|
||||
"nuschtos": "nuschtos",
|
||||
"systems": "systems_2",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
}
|
||||
},
|
||||
"systems": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"systems_2": {
|
||||
"locked": {
|
||||
"lastModified": 1681028828,
|
||||
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "nix-systems",
|
||||
"repo": "default",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"treefmt-nix": {
|
||||
"inputs": {
|
||||
"nixpkgs": []
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1750931469,
|
||||
"narHash": "sha256-0IEdQB1nS+uViQw4k3VGUXntjkDp7aAlqcxdewb/hAc=",
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"rev": "ac8e6f32e11e9c7f153823abc3ab007f2a65d3e1",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "treefmt-nix",
|
||||
"type": "github"
|
||||
}
|
||||
}
|
||||
},
|
||||
"root": "root",
|
||||
"version": 7
|
||||
}
|
||||
@@ -1,19 +0,0 @@
|
||||
{
|
||||
description = "private dev inputs";
|
||||
|
||||
# Dev dependencies
|
||||
inputs.nixpkgs-dev.url = "github:NixOS/nixpkgs/nixos-unstable-small";
|
||||
|
||||
inputs.flake-utils.url = "github:numtide/flake-utils";
|
||||
inputs.flake-utils.inputs.systems.follows = "systems";
|
||||
|
||||
inputs.nuschtos.url = "github:NuschtOS/search";
|
||||
inputs.nuschtos.inputs.nixpkgs.follows = "nixpkgs-dev";
|
||||
|
||||
inputs.treefmt-nix.url = "github:numtide/treefmt-nix";
|
||||
inputs.treefmt-nix.inputs.nixpkgs.follows = "";
|
||||
|
||||
inputs.systems.url = "github:nix-systems/default";
|
||||
|
||||
outputs = _: { };
|
||||
}
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env bash
|
||||
# Used to update the private dev flake hash reference.
|
||||
set -euo pipefail
|
||||
|
||||
cd "$(dirname "$0")"
|
||||
|
||||
echo "Updating $PWD/private.narHash" >&2
|
||||
|
||||
nix --extra-experimental-features 'flakes nix-command' flake lock ./private
|
||||
nix --extra-experimental-features 'flakes nix-command' hash path ./private >./private.narHash
|
||||
|
||||
echo OK
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
clan-core,
|
||||
pkgs,
|
||||
module-docs,
|
||||
clan-cli-docs,
|
||||
@@ -18,17 +19,7 @@ pkgs.stdenv.mkDerivation {
|
||||
|
||||
# Points to repository root.
|
||||
# so that we can access directories outside of docs to include code snippets
|
||||
src = pkgs.lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = pkgs.lib.fileset.unions [
|
||||
# Docs directory
|
||||
../../docs
|
||||
# Icons needed for the build
|
||||
../../pkgs/clan-app/ui/icons
|
||||
# Any other directories that might be referenced for code snippets
|
||||
# Add them here as needed based on what mkdocs actually uses
|
||||
];
|
||||
};
|
||||
src = clan-core;
|
||||
|
||||
nativeBuildInputs =
|
||||
[
|
||||
|
||||
@@ -82,9 +82,10 @@
|
||||
}
|
||||
''
|
||||
export CLAN_CORE_PATH=${
|
||||
inputs.nixpkgs.lib.fileset.toSource {
|
||||
root = ../..;
|
||||
fileset = ../../clanModules;
|
||||
self.filter {
|
||||
include = [
|
||||
"clanModules"
|
||||
];
|
||||
}
|
||||
}
|
||||
export CLAN_CORE_DOCS=${jsonDocs.clanCore}/share/doc/nixos/options.json
|
||||
@@ -125,6 +126,7 @@
|
||||
});
|
||||
packages = {
|
||||
docs = pkgs.python3.pkgs.callPackage ./default.nix {
|
||||
clan-core = self;
|
||||
inherit (self'.packages)
|
||||
clan-cli-docs
|
||||
docs-options
|
||||
|
||||
@@ -1,15 +1,9 @@
|
||||
{
|
||||
self,
|
||||
config,
|
||||
inputs,
|
||||
privateInputs ? { },
|
||||
...
|
||||
}:
|
||||
{ self, config, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{
|
||||
inputs',
|
||||
lib,
|
||||
pkgs,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -163,16 +157,11 @@
|
||||
};
|
||||
in
|
||||
{
|
||||
packages = lib.optionalAttrs ((privateInputs ? nuschtos) || (inputs ? nuschtos)) {
|
||||
docs-options =
|
||||
(privateInputs.nuschtos or inputs.nuschtos)
|
||||
.packages.${pkgs.stdenv.hostPlatform.system}.mkMultiSearch
|
||||
{
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
|
||||
};
|
||||
packages.docs-options = inputs'.nuschtos.packages.mkMultiSearch {
|
||||
inherit baseHref;
|
||||
title = "Clan Options";
|
||||
# scopes = mapAttrsToList mkScope serviceModules;
|
||||
scopes = [ (mkScope "Clan Inventory" serviceModules) ];
|
||||
};
|
||||
};
|
||||
}
|
||||
|
||||
@@ -105,7 +105,7 @@ git+file:///home/lhebendanz/Projects/clan-core
|
||||
│ ├───editor omitted (use '--all-systems' to show)
|
||||
└───templates
|
||||
├───default: template: Initialize a new clan flake
|
||||
└───default: template: Initialize a new clan flake
|
||||
└───new-clan: template: Initialize a new clan flake
|
||||
```
|
||||
|
||||
You can execute every test separately by following the tree path `nix run .#checks.x86_64-linux.clan-pytest -L` for example.
|
||||
|
||||
72
flake.lock
generated
72
flake.lock
generated
@@ -67,6 +67,52 @@
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"flake-utils": {
|
||||
"inputs": {
|
||||
"systems": [
|
||||
"systems"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1731533236,
|
||||
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "numtide",
|
||||
"repo": "flake-utils",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"ixx": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"nuschtos",
|
||||
"flake-utils"
|
||||
],
|
||||
"nixpkgs": [
|
||||
"nuschtos",
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1748294338,
|
||||
"narHash": "sha256-FVO01jdmUNArzBS7NmaktLdGA5qA3lUMJ4B7a05Iynw=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "ixx",
|
||||
"rev": "cc5f390f7caf265461d4aab37e98d2292ebbdb85",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"ref": "v0.0.8",
|
||||
"repo": "ixx",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"nix-darwin": {
|
||||
"inputs": {
|
||||
"nixpkgs": [
|
||||
@@ -128,15 +174,41 @@
|
||||
"url": "https://nixos.org/channels/nixpkgs-unstable/nixexprs.tar.xz"
|
||||
}
|
||||
},
|
||||
"nuschtos": {
|
||||
"inputs": {
|
||||
"flake-utils": [
|
||||
"flake-utils"
|
||||
],
|
||||
"ixx": "ixx",
|
||||
"nixpkgs": [
|
||||
"nixpkgs"
|
||||
]
|
||||
},
|
||||
"locked": {
|
||||
"lastModified": 1749730855,
|
||||
"narHash": "sha256-L3x2nSlFkXkM6tQPLJP3oCBMIsRifhIDPMQQdHO5xWo=",
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"rev": "8dfe5879dd009ff4742b668d9c699bc4b9761742",
|
||||
"type": "github"
|
||||
},
|
||||
"original": {
|
||||
"owner": "NuschtOS",
|
||||
"repo": "search",
|
||||
"type": "github"
|
||||
}
|
||||
},
|
||||
"root": {
|
||||
"inputs": {
|
||||
"data-mesher": "data-mesher",
|
||||
"disko": "disko",
|
||||
"flake-parts": "flake-parts",
|
||||
"flake-utils": "flake-utils",
|
||||
"nix-darwin": "nix-darwin",
|
||||
"nix-select": "nix-select",
|
||||
"nixos-facter-modules": "nixos-facter-modules",
|
||||
"nixpkgs": "nixpkgs",
|
||||
"nuschtos": "nuschtos",
|
||||
"sops-nix": "sops-nix",
|
||||
"systems": "systems",
|
||||
"treefmt-nix": "treefmt-nix"
|
||||
|
||||
23
flake.nix
23
flake.nix
@@ -35,13 +35,19 @@
|
||||
};
|
||||
};
|
||||
|
||||
# dependencies needed for nuschtos
|
||||
flake-utils.url = "github:numtide/flake-utils";
|
||||
flake-utils.inputs.systems.follows = "systems";
|
||||
nuschtos.url = "github:NuschtOS/search";
|
||||
nuschtos.inputs.nixpkgs.follows = "nixpkgs";
|
||||
nuschtos.inputs.flake-utils.follows = "flake-utils";
|
||||
};
|
||||
|
||||
outputs =
|
||||
inputs@{
|
||||
flake-parts,
|
||||
nixpkgs,
|
||||
systems,
|
||||
flake-parts,
|
||||
...
|
||||
}:
|
||||
let
|
||||
@@ -50,25 +56,10 @@
|
||||
optional
|
||||
pathExists
|
||||
;
|
||||
|
||||
loadDevFlake =
|
||||
path:
|
||||
let
|
||||
flakeHash = nixpkgs.lib.fileContents "${toString path}.narHash";
|
||||
flakePath = "path:${toString path}?narHash=${flakeHash}";
|
||||
in
|
||||
builtins.getFlake (builtins.unsafeDiscardStringContext flakePath);
|
||||
|
||||
devFlake = builtins.tryEval (loadDevFlake ./devFlake/private);
|
||||
|
||||
privateInputs = if devFlake.success then devFlake.value.inputs else { };
|
||||
in
|
||||
flake-parts.lib.mkFlake { inherit inputs; } (
|
||||
{ ... }:
|
||||
{
|
||||
_module.args = {
|
||||
inherit privateInputs;
|
||||
};
|
||||
clan = {
|
||||
meta.name = "clan-core";
|
||||
inventory = {
|
||||
|
||||
@@ -4,7 +4,7 @@
|
||||
perSystem =
|
||||
{ self', pkgs, ... }:
|
||||
{
|
||||
treefmt.projectRootFile = "LICENSE.md";
|
||||
treefmt.projectRootFile = ".git/config";
|
||||
treefmt.programs.shellcheck.enable = true;
|
||||
|
||||
treefmt.programs.mypy.enable = true;
|
||||
|
||||
@@ -37,7 +37,6 @@ lib.fix (
|
||||
inventory = clanLib.callLib ./modules/inventory { };
|
||||
modules = clanLib.callLib ./modules/inventory/frontmatter { };
|
||||
test = clanLib.callLib ./test { };
|
||||
flake-inputs = clanLib.callLib ./flake-inputs.nix { };
|
||||
# Custom types
|
||||
types = clanLib.callLib ./types { };
|
||||
|
||||
|
||||
@@ -1,18 +0,0 @@
|
||||
{ ... }:
|
||||
{
|
||||
/**
|
||||
Generate nix-unit input overrides for tests
|
||||
|
||||
# Example
|
||||
```nix
|
||||
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
|
||||
```
|
||||
*/
|
||||
getOverrides =
|
||||
inputs:
|
||||
builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (
|
||||
builtins.filter (name: name != "self") (builtins.attrNames inputs)
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -1,6 +1,8 @@
|
||||
{ self, inputs, ... }:
|
||||
let
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
|
||||
@@ -4,7 +4,9 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
{ self, inputs, ... }:
|
||||
let
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
@@ -10,23 +12,6 @@ in
|
||||
system,
|
||||
...
|
||||
}:
|
||||
let
|
||||
# Common filtered source for inventory tests
|
||||
inventoryTestsSrc = lib.fileset.toSource {
|
||||
root = ../../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../../flake.nix
|
||||
../../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../../..)
|
||||
../../../../flakeModules
|
||||
../../../../lib
|
||||
../../../../nixosModules/clanCore
|
||||
../../../../clanModules/borgbackup
|
||||
../../../../machines
|
||||
../../../../inventory.json
|
||||
];
|
||||
};
|
||||
in
|
||||
{
|
||||
# Run: nix-unit --extra-experimental-features flakes --flake .#legacyPackages.x86_64-linux.<attrName>
|
||||
legacyPackages.evalTests-distributedServices = import ./tests {
|
||||
@@ -44,7 +29,7 @@ in
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${inventoryTestsSrc}#legacyPackages.${system}.evalTests-distributedServices
|
||||
--flake ${self}#legacyPackages.${system}.evalTests-distributedServices
|
||||
|
||||
touch $out
|
||||
'';
|
||||
@@ -54,7 +39,7 @@ in
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${inventoryTestsSrc}#legacyPackages.${system}.eval-tests-resolve-module
|
||||
--flake ${self}#legacyPackages.${system}.eval-tests-resolve-module
|
||||
|
||||
touch $out
|
||||
'';
|
||||
|
||||
@@ -5,7 +5,9 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
in
|
||||
{
|
||||
imports = [
|
||||
@@ -68,18 +70,12 @@ in
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${
|
||||
lib.fileset.toSource {
|
||||
root = ../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../flake.nix
|
||||
../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
|
||||
../../../flakeModules
|
||||
../../../lib
|
||||
../../../nixosModules/clanCore
|
||||
../../../clanModules/borgbackup
|
||||
../../../machines
|
||||
../../../inventory.json
|
||||
self.filter {
|
||||
include = [
|
||||
"flakeModules"
|
||||
"lib"
|
||||
"clanModules/flake-module.nix"
|
||||
"clanModules/borgbackup"
|
||||
];
|
||||
}
|
||||
}#legacyPackages.${system}.evalTests-inventory
|
||||
|
||||
@@ -16,7 +16,7 @@
|
||||
*/
|
||||
makeEvalChecks =
|
||||
{
|
||||
fileset,
|
||||
self,
|
||||
inputs,
|
||||
testName,
|
||||
tests,
|
||||
@@ -24,7 +24,9 @@
|
||||
testArgs ? { },
|
||||
}:
|
||||
let
|
||||
inputOverrides = clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
attrName = "eval-tests-${testName}";
|
||||
in
|
||||
{
|
||||
@@ -39,44 +41,16 @@
|
||||
}
|
||||
// testArgs
|
||||
);
|
||||
checks.${attrName} =
|
||||
let
|
||||
# The root is two directories up from where this file is located
|
||||
root = ../..;
|
||||
checks.${attrName} = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
|
||||
# Combine the user-provided fileset with all flake-module.nix files
|
||||
# and other essential files
|
||||
src = lib.fileset.toSource {
|
||||
inherit root;
|
||||
fileset = lib.fileset.unions [
|
||||
# Core flake files
|
||||
(root + "/flake.nix")
|
||||
(root + "/flake.lock")
|
||||
|
||||
# All flake-module.nix files anywhere in the tree
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") root)
|
||||
|
||||
# The flakeModules/clan.nix if it exists
|
||||
(lib.fileset.maybeMissing (root + "/flakeModules/clan.nix"))
|
||||
|
||||
# Core libraries
|
||||
(root + "/lib")
|
||||
|
||||
# User-provided fileset
|
||||
fileset
|
||||
];
|
||||
};
|
||||
in
|
||||
pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
|
||||
nix-unit --eval-store "$HOME" \
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${src}#legacyPackages.${system}.${attrName}
|
||||
touch $out
|
||||
'';
|
||||
nix-unit --eval-store "$HOME" \
|
||||
--extra-experimental-features flakes \
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${self}#legacyPackages.${system}.${attrName}
|
||||
touch $out
|
||||
'';
|
||||
|
||||
};
|
||||
}
|
||||
|
||||
@@ -1,9 +1,4 @@
|
||||
{
|
||||
self,
|
||||
inputs,
|
||||
lib,
|
||||
...
|
||||
}:
|
||||
{ self, inputs, ... }:
|
||||
{
|
||||
perSystem =
|
||||
{ ... }:
|
||||
@@ -15,11 +10,7 @@
|
||||
test-types-module = (
|
||||
self.clanLib.test.flakeModules.makeEvalChecks {
|
||||
module = throw "";
|
||||
inherit inputs;
|
||||
fileset = lib.fileset.unions [
|
||||
# Only lib is needed for type tests
|
||||
../../lib
|
||||
];
|
||||
inherit self inputs;
|
||||
testName = "types";
|
||||
tests = ./tests.nix;
|
||||
# Optional arguments passed to the test
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{ lib, pkgs }:
|
||||
{ lib, pkgs, ... }:
|
||||
let
|
||||
eval =
|
||||
module:
|
||||
|
||||
@@ -5,14 +5,18 @@
|
||||
...
|
||||
}:
|
||||
let
|
||||
inputOverrides = self.clanLib.flake-inputs.getOverrides inputs;
|
||||
inputOverrides = builtins.concatStringsSep " " (
|
||||
builtins.map (input: " --override-input ${input} ${inputs.${input}}") (builtins.attrNames inputs)
|
||||
);
|
||||
in
|
||||
{
|
||||
perSystem =
|
||||
{ system, pkgs, ... }:
|
||||
{
|
||||
legacyPackages.evalTests-module-clan-vars = import ./eval-tests {
|
||||
inherit lib pkgs;
|
||||
inherit lib;
|
||||
clan-core = self;
|
||||
pkgs = inputs.nixpkgs.legacyPackages.${system};
|
||||
};
|
||||
checks.eval-module-clan-vars = pkgs.runCommand "tests" { nativeBuildInputs = [ pkgs.nix-unit ]; } ''
|
||||
export HOME="$(realpath .)"
|
||||
@@ -22,15 +26,11 @@ in
|
||||
--show-trace \
|
||||
${inputOverrides} \
|
||||
--flake ${
|
||||
lib.fileset.toSource {
|
||||
root = ../../..;
|
||||
fileset = lib.fileset.unions [
|
||||
../../../flake.nix
|
||||
../../../flake.lock
|
||||
(lib.fileset.fileFilter (file: file.name == "flake-module.nix") ../../..)
|
||||
../../../flakeModules/clan.nix
|
||||
../../../lib
|
||||
../../../nixosModules/clanCore/vars
|
||||
self.filter {
|
||||
include = [
|
||||
"flakeModules"
|
||||
"nixosModules"
|
||||
"lib"
|
||||
];
|
||||
}
|
||||
}#legacyPackages.${system}.evalTests-module-clan-vars
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import type { Preview } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
import "@/src/components/v2/index.css";
|
||||
import "../src/index.css";
|
||||
import "./preview.css";
|
||||
|
||||
|
||||
10
pkgs/clan-app/ui/icons/clan-logo.svg
Normal file
10
pkgs/clan-app/ui/icons/clan-logo.svg
Normal file
@@ -0,0 +1,10 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="223" height="89" fill="currentColor">
|
||||
<g clip-path="url(#a)">
|
||||
<path d="M55.503 18.696h10.104a1.946 1.946 0 0 0 1.943-1.948v-7.79c0-1.075-.87-1.947-1.943-1.947h-3.186a1.863 1.863 0 0 1-1.866-1.87V1.947C60.555.872 59.685 0 58.612 0h-27.98a1.946 1.946 0 0 0-1.944 1.947v3.194c0 1.036-.832 1.87-1.865 1.87h-3.187a1.946 1.946 0 0 0-1.943 1.947v3.194c0 1.036-.832 1.87-1.866 1.87h-3.186a1.946 1.946 0 0 0-1.943 1.947s-.467 1.153-.467 23.253c0 19.763.467 21.913.467 21.913 0 1.075.87 1.948 1.943 1.948h3.186c1.034 0 1.866.833 1.866 1.87v3.271c0 1.036.831 1.87 1.865 1.87h3.265c1.033 0 1.865.833 1.865 1.87v3.193c0 1.075.87 1.948 1.943 1.948h27.981a1.946 1.946 0 0 0 1.943-1.948v-3.194c0-1.036.832-1.87 1.866-1.87h5.145a1.946 1.946 0 0 0 1.943-1.947v-9.285c0-1.075-.87-1.948-1.943-1.948H55.503a1.946 1.946 0 0 0-1.943 1.948v4.69c0 1.035-.832 1.869-1.866 1.869H37.55a1.863 1.863 0 0 1-1.866-1.87v-4.752c0-1.075-.87-1.947-1.943-1.947H29c-1.034 0-1.609.148-1.865-1.87-.078-.646-.125-1.44-.18-2.508-.147-2.68-.287-5.5-.287-13.539 0-11.24.288-16.81.466-18.369.18-1.558.832-1.87 1.866-1.87h4.741a1.946 1.946 0 0 0 1.943-1.947v-3.193c0-1.037.832-1.87 1.866-1.87h14.145c1.034 0 1.866.833 1.866 1.87v3.193c0 1.075.87 1.948 1.943 1.948M20.247 74.822h-2.293a.814.814 0 0 1-.808-.81v-2.298c0-.896-.723-1.62-1.617-1.62H9.327c-.894 0-1.617.724-1.617 1.62v2.298c0 .444-.365.81-.808.81H4.609c-.894 0-1.617.725-1.617 1.62v6.217c0 .896.723 1.62 1.617 1.62h2.293c.443 0 .808.366.808.81v2.299c0 .895.723 1.62 1.617 1.62h6.202c.894 0 1.617-.725 1.617-1.62v-2.299c0-.444.365-.81.808-.81h2.293c.894 0 1.617-.724 1.617-1.62v-6.216c0-.896-.723-1.62-1.617-1.62M221.135 35.04h-1.71a1.863 1.863 0 0 1-1.866-1.87v-3.272c0-1.036-.831-1.87-1.865-1.87h-3.265a1.863 1.863 0 0 1-1.865-1.87v-3.271c0-1.036-.832-1.87-1.865-1.87h-20.971a1.863 1.863 0 0 0-1.865 1.87v3.965c0 .514-.42.935-.933.935h-3.559c-.513 0-.84-.32-.933-.935l-.622-3.918c-.148-1.099-.676-1.777-1.788-1.777l-3.653-.14h-2.052a3.736 3.736 0 0 0-3.73 3.74V61.68a3.736 3.736 0 0 1-3.731 3.739h-8.394a1.863 1.863 0 0 1-1.866-1.87V36.714c0-11.825-7.461-18.813-22.556-18.813-13.718 0-20.325 5.04-21.203 14.443-.109 1.153.552 1.815 1.702 1.815l7.757.569c1.143.1 1.594-.554 1.811-1.652.77-3.74 4.174-5.827 9.933-5.827 7.081 0 10.042 3.358 10.042 9.076v3.014c0 1.036-.831 1.87-1.865 1.87l-.342-.024h-9.715c-15.421 0-22.984 5.983-22.984 17.956 0 3.802.778 7.058 2.254 9.738h-.59c-1.765-1.27-2.457-2.236-3.055-2.93-.256-.295-.653-.537-1.345-.537h-1.717l-5.993.008h-3.264a3.736 3.736 0 0 1-3.731-3.74V1.769C89.74.654 89.072 0 87.969 0H79.55c-1.034 0-1.865.732-1.865 1.768l-.024 54.304v13.554c0 4.13 3.343 7.479 7.462 7.479h50.84c8.448-.429 8.604-3.42 9.436-4.542.645 3.56 1.865 4.347 4.71 4.518 8.137.117 18.343.032 18.49.024h4.975c4.119 0 6.684-3.35 6.684-7.479l.777-27.264c0-1.036.832-1.87 1.866-1.87h2.021a1.56 1.56 0 0 0 1.554-1.558v-3.583c0-1.036.832-1.87 1.866-1.87h11.868a3.37 3.37 0 0 1 3.366 3.373v3.249c0 1.075.87 1.947 1.943 1.947h4.119c.513 0 .933.42.933.935v32.25c0 1.036.831 1.87 1.865 1.87h6.84a3.736 3.736 0 0 0 3.731-3.74V36.91c0-1.036-.832-1.87-1.866-1.87zM142.64 54.225c0 8.927-6.132 14.715-15.335 14.715-6.606 0-9.793-2.953-9.793-8.748 0-6.442 3.832-9.636 11.62-9.636h13.508v3.669"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="a">
|
||||
<path d="M0 0h223v89H0z"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 3.3 KiB |
@@ -1,5 +0,0 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 64 26" fill="none">
|
||||
<path d="M15.2735 5.89115H18.2129C18.5249 5.89115 18.7781 5.63627 18.7781 5.32222V3.04651C18.7781 2.73246 18.5249 2.47758 18.2129 2.47758H17.2858C16.9851 2.47758 16.7432 2.23408 16.7432 1.93141V0.998371C16.7432 0.684323 16.4899 0.429443 16.1779 0.429443H8.03798C7.72595 0.429443 7.47271 0.684323 7.47271 0.998371V1.93141C7.47271 2.23408 7.23077 2.47758 6.93005 2.47758H6.003C5.69097 2.47758 5.43773 2.73246 5.43773 3.04651V3.97955C5.43773 4.28222 5.1958 4.52572 4.89507 4.52572H3.96803C3.656 4.52572 3.40276 4.7806 3.40276 5.09465C3.40276 5.09465 3.26709 5.43146 3.26709 11.4582C3.26709 17.2317 3.40276 17.8598 3.40276 17.8598C3.40276 18.1739 3.656 18.4287 3.96803 18.4287H4.89507C5.1958 18.4287 5.43773 18.6722 5.43773 18.9749V19.9307C5.43773 20.2334 5.67967 20.4769 5.98039 20.4769H6.93005C7.23077 20.4769 7.47271 20.7204 7.47271 21.023V21.9561C7.47271 22.2701 7.72595 22.525 8.03798 22.525H16.1779C16.4899 22.525 16.7432 22.2701 16.7432 21.9561V21.023C16.7432 20.7204 16.9851 20.4769 17.2858 20.4769H18.7827C19.0947 20.4769 19.3479 20.222 19.3479 19.9079V17.1953C19.3479 16.8812 19.0947 16.6264 18.7827 16.6264H15.2735C14.9614 16.6264 14.7082 16.8812 14.7082 17.1953V18.5653C14.7082 18.8679 14.4662 19.1114 14.1655 19.1114H10.0503C9.74962 19.1114 9.50768 18.8679 9.50768 18.5653V17.1771C9.50768 16.863 9.25444 16.6082 8.94241 16.6082H7.56315C7.26243 16.6082 7.09511 16.6514 7.02049 16.062C6.99788 15.8731 6.98431 15.641 6.96849 15.3292C6.92552 14.5464 6.88483 13.7226 6.88483 11.374C6.88483 8.5196 6.96849 6.89246 7.02049 6.43732C7.0725 5.98218 7.26243 5.89115 7.56315 5.89115H8.94241C9.25444 5.89115 9.50768 5.63627 9.50768 5.32222V4.38918C9.50768 4.08651 9.74962 3.84301 10.0503 3.84301H14.1655C14.4662 3.84301 14.7082 4.08651 14.7082 4.38918V5.32222C14.7082 5.63627 14.9614 5.89115 15.2735 5.89115Z" fill="currentColor"/>
|
||||
<path d="M5.01717 21.8582H4.35015C4.22126 21.8582 4.11499 21.7513 4.11499 21.6216V20.9502C4.11499 20.6885 3.90471 20.4769 3.64469 20.4769H1.84034C1.58032 20.4769 1.37004 20.6885 1.37004 20.9502V21.6216C1.37004 21.7513 1.26377 21.8582 1.13488 21.8582H0.467864C0.207839 21.8582 -0.00244141 22.0699 -0.00244141 22.3316V24.1476C-0.00244141 24.4093 0.207839 24.6209 0.467864 24.6209H1.13488C1.26377 24.6209 1.37004 24.7279 1.37004 24.8576V25.5289C1.37004 25.7907 1.58032 26.0023 1.84034 26.0023H3.64469C3.90471 26.0023 4.11499 25.7907 4.11499 25.5289V24.8576C4.11499 24.7279 4.22126 24.6209 4.35015 24.6209H5.01717C5.27719 24.6209 5.48747 24.4093 5.48747 24.1476V22.3316C5.48747 22.0699 5.27719 21.8582 5.01717 21.8582Z" fill="currentColor"/>
|
||||
<path d="M63.4576 10.2361H62.9601C62.6594 10.2361 62.4175 9.99265 62.4175 9.68998V8.73418C62.4175 8.43151 62.1755 8.18801 61.8748 8.18801H60.9252C60.6244 8.18801 60.3825 7.94451 60.3825 7.64184V6.68604C60.3825 6.38337 60.1406 6.13987 59.8398 6.13987H53.7394C53.4387 6.13987 53.1968 6.38337 53.1968 6.68604V7.84438C53.1968 7.99457 53.0747 8.11746 52.9254 8.11746H51.8899C51.7406 8.11746 51.6457 8.02416 51.6185 7.84438L51.4376 6.69969C51.3947 6.37882 51.2409 6.18083 50.9176 6.18083L49.8549 6.13987H49.258C48.6588 6.13987 48.1726 6.62915 48.1726 7.23221V18.0191C48.1726 18.6221 47.6865 19.1114 47.0873 19.1114H44.6453C44.3446 19.1114 44.1027 18.8679 44.1027 18.5653V10.7254C44.1027 7.2709 41.932 5.22958 37.541 5.22958C33.5502 5.22958 31.6283 6.70197 31.3728 9.44875C31.3411 9.78556 31.5333 9.97899 31.868 9.97899L34.1245 10.1451C34.4569 10.1747 34.588 9.98354 34.6514 9.66267C34.8752 8.57033 35.8656 7.96044 37.541 7.96044C39.6009 7.96044 40.4623 8.94127 40.4623 10.6116V11.4923C40.4623 11.795 40.2204 12.0385 39.9197 12.0385L39.8202 12.0317H36.9938C32.5078 12.0317 30.3078 13.7794 30.3078 17.2772C30.3078 18.3877 30.5339 19.339 30.9635 20.1218H30.7917C30.2784 19.7509 30.0772 19.4687 29.9031 19.2662C29.8285 19.1797 29.7131 19.1091 29.5119 19.1091H29.0122L27.2689 19.1114H26.3193C25.7201 19.1114 25.2339 18.6221 25.2339 18.0191V0.516586C25.2339 0.19116 25.0395 0 24.7184 0H22.2697C21.9689 0 21.727 0.213917 21.727 0.516586L21.7202 16.3806V20.3403C21.7202 21.5464 22.6925 22.525 23.8909 22.525H38.6806C41.1384 22.3998 41.1836 21.5259 41.4256 21.1982C41.6132 22.2382 41.9682 22.4681 42.7958 22.5182C45.1631 22.5523 48.1319 22.5273 48.1749 22.525H49.622C50.8204 22.525 51.5665 21.5464 51.5665 20.3403L51.7926 12.3753C51.7926 12.0726 52.0346 11.8291 52.3353 11.8291H52.9232C53.1719 11.8291 53.3754 11.6243 53.3754 11.374V10.3272C53.3754 10.0245 53.6173 9.781 53.9181 9.781H57.3707C57.9111 9.781 58.3498 10.2225 58.3498 10.7664V11.7154C58.3498 12.0294 58.603 12.2843 58.915 12.2843H60.1134C60.2626 12.2843 60.3848 12.4072 60.3848 12.5574V21.9788C60.3848 22.2815 60.6267 22.525 60.9274 22.525H62.9172C63.5164 22.525 64.0025 22.0357 64.0025 21.4326V10.7823C64.0025 10.4796 63.7606 10.2361 63.4598 10.2361H63.4576ZM40.6229 15.8412C40.6229 18.4492 38.8389 20.14 36.1618 20.14C34.2398 20.14 33.3128 19.2775 33.3128 17.5844C33.3128 15.7024 34.4275 14.7694 36.6931 14.7694H40.6229V15.8389V15.8412Z" fill="currentColor"/>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 4.9 KiB |
@@ -1,43 +0,0 @@
|
||||
<svg viewBox="0 0 200 21" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<path d="M191.879 19.297C191.736 19.297 191.602 19.2434 191.477 19.1362C191.37 19.029 191.316 18.9039 191.316 18.761V6.16432C191.316 6.02138 191.37 5.8963 191.477 5.7891C191.602 5.68189 191.736 5.62829 191.879 5.62829H193.246C193.568 5.62829 193.764 5.80697 193.836 6.16432C193.907 6.48594 193.979 6.72715 194.05 6.88796C194.139 7.0309 194.273 7.10237 194.452 7.10237C194.559 7.10237 194.676 7.06663 194.801 6.99516C194.926 6.90582 195.051 6.80755 195.176 6.70035C195.569 6.36086 195.971 6.10178 196.382 5.92311C196.793 5.72656 197.365 5.62829 198.097 5.62829H199.464C199.607 5.62829 199.732 5.68189 199.839 5.7891C199.946 5.8963 200 6.02138 200 6.16432V8.28163C200 8.42457 199.946 8.54964 199.839 8.65685C199.732 8.76405 199.607 8.81766 199.464 8.81766H197.936C196.9 8.81766 196.042 9.12141 195.363 9.7289C194.702 10.3364 194.372 11.2745 194.372 12.543V18.761C194.372 18.9039 194.318 19.029 194.211 19.1362C194.104 19.2434 193.979 19.297 193.836 19.297H191.879Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M182.709 19.7526C180.583 19.7526 178.93 19.0915 177.751 17.7693C176.589 16.4471 176.009 14.6782 176.009 12.4626C176.009 11.0154 176.268 9.74677 176.786 8.65685C177.304 7.54906 178.064 6.69141 179.064 6.08391C180.065 5.47642 181.253 5.17267 182.629 5.17267C184.648 5.17267 186.238 5.7623 187.399 6.94156C188.579 8.12082 189.168 9.74677 189.168 11.8194V12.7843C189.168 13.0165 189.106 13.1952 188.981 13.3203C188.856 13.4454 188.686 13.5079 188.471 13.5079H179.573C179.395 13.5079 179.305 13.6062 179.305 13.8027C179.341 14.839 179.681 15.6609 180.324 16.2684C180.967 16.8581 181.762 17.1529 182.709 17.1529C184.031 17.1529 184.978 16.6526 185.55 15.652C185.747 15.3304 185.97 15.1696 186.22 15.1696H188.284C188.445 15.1696 188.579 15.2321 188.686 15.3572C188.793 15.4644 188.82 15.6163 188.766 15.8128C188.427 16.9921 187.73 17.948 186.676 18.6806C185.622 19.3953 184.299 19.7526 182.709 19.7526ZM179.279 10.801C179.279 10.9796 179.377 11.069 179.573 11.069H185.791C185.952 11.069 186.032 10.9796 186.032 10.801C186.032 9.92545 185.72 9.19288 185.094 8.60325C184.487 7.99575 183.665 7.692 182.629 7.692C181.664 7.692 180.869 7.99575 180.243 8.60325C179.636 9.19288 179.314 9.92545 179.279 10.801Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M166.551 19.7526C165.621 19.7526 164.773 19.6008 164.004 19.297C163.236 18.9754 162.584 18.5108 162.048 17.9033C161.44 17.2601 160.976 16.4739 160.654 15.5448C160.333 14.6157 160.172 13.5883 160.172 12.4627C160.172 11.337 160.333 10.3096 160.654 9.38049C160.976 8.45137 161.44 7.6652 162.048 7.02197C162.584 6.43234 163.236 5.97671 164.004 5.6551C164.773 5.33348 165.621 5.17267 166.551 5.17267C167.247 5.17267 167.801 5.24414 168.212 5.38708C168.641 5.51215 169.079 5.69083 169.526 5.92311C169.561 5.94098 169.66 5.99458 169.82 6.08392C169.981 6.17326 170.133 6.21792 170.276 6.21792C170.598 6.21792 170.758 6.03032 170.758 5.6551V0.536027C170.758 0.393087 170.812 0.268014 170.919 0.160808C171.026 0.0536026 171.16 0 171.321 0H173.305C173.447 0 173.573 0.0536026 173.68 0.160808C173.787 0.268014 173.841 0.393087 173.841 0.536027V18.761C173.841 18.9039 173.787 19.029 173.68 19.1362C173.573 19.2434 173.447 19.297 173.305 19.297H171.911C171.661 19.297 171.446 19.1541 171.268 18.8682C171.16 18.7252 171.053 18.618 170.946 18.5466C170.857 18.4751 170.732 18.4394 170.571 18.4394C170.356 18.4394 170.062 18.5644 169.686 18.8146C169.24 19.1005 168.793 19.3238 168.346 19.4846C167.9 19.6633 167.301 19.7526 166.551 19.7526ZM163.308 12.4627C163.308 13.6955 163.62 14.6961 164.246 15.4644C164.567 15.8575 164.969 16.1702 165.452 16.4025C165.934 16.6169 166.488 16.7241 167.113 16.7241C168.328 16.7241 169.284 16.3042 169.981 15.4644C170.589 14.6961 170.892 13.6955 170.892 12.4627C170.892 11.2298 170.589 10.2381 169.981 9.4877C169.302 8.63005 168.346 8.20123 167.113 8.20123C166.488 8.20123 165.934 8.31737 165.452 8.54965C164.969 8.76406 164.567 9.07674 164.246 9.4877C163.62 10.2203 163.308 11.2119 163.308 12.4627Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M155.415 19.297C155.272 19.297 155.147 19.2434 155.04 19.1362C154.933 19.029 154.879 18.9039 154.879 18.761V0.536027C154.879 0.393087 154.933 0.268014 155.04 0.160808C155.147 0.0536026 155.272 0 155.415 0H157.399C157.542 0 157.667 0.0536026 157.774 0.160808C157.881 0.268014 157.935 0.393087 157.935 0.536027V18.761C157.935 18.9039 157.881 19.029 157.774 19.1362C157.667 19.2434 157.542 19.297 157.399 19.297H155.415Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M149.107 19.297C148.946 19.297 148.812 19.2434 148.705 19.1362C148.615 19.029 148.571 18.9039 148.571 18.761V6.16432C148.571 6.02138 148.615 5.89631 148.705 5.7891C148.812 5.68189 148.946 5.62829 149.107 5.62829H151.09C151.251 5.62829 151.376 5.68189 151.465 5.7891C151.573 5.87844 151.626 6.00351 151.626 6.16432V18.761C151.626 18.9218 151.573 19.0558 151.465 19.163C151.376 19.2523 151.251 19.297 151.09 19.297H149.107ZM148.33 0.696836C148.33 0.58963 148.365 0.491358 148.437 0.40202C148.526 0.312682 148.633 0.268013 148.758 0.268013H151.492C151.617 0.268013 151.715 0.312682 151.787 0.40202C151.876 0.491358 151.921 0.58963 151.921 0.696836V3.18937C151.921 3.31444 151.876 3.42164 151.787 3.51098C151.715 3.58245 151.617 3.61819 151.492 3.61819H148.758C148.633 3.61819 148.526 3.58245 148.437 3.51098C148.365 3.42164 148.33 3.31444 148.33 3.18937V0.696836Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M105.963 19.7526C105.213 19.7526 104.614 19.6633 104.168 19.4846C103.721 19.3238 103.274 19.1005 102.828 18.8146C102.452 18.5644 102.158 18.4394 101.943 18.4394C101.782 18.4394 101.648 18.4751 101.541 18.5466C101.452 18.618 101.353 18.7252 101.246 18.8682C101.068 19.1541 100.853 19.297 100.603 19.297H99.2094C99.0664 19.297 98.9414 19.2434 98.8342 19.1362C98.7269 19.029 98.6733 18.9039 98.6733 18.761V0.536027C98.6733 0.393087 98.7269 0.268014 98.8342 0.160808C98.9414 0.0536026 99.0664 0 99.2094 0H101.193C101.336 0 101.461 0.0536026 101.568 0.160808C101.675 0.268014 101.729 0.393087 101.729 0.536027V5.6551C101.729 6.03032 101.898 6.21792 102.238 6.21792C102.381 6.21792 102.533 6.17326 102.694 6.08392C102.854 5.99458 102.953 5.94098 102.988 5.92311C103.435 5.69083 103.864 5.51215 104.275 5.38708C104.704 5.24414 105.266 5.17267 105.963 5.17267C106.892 5.17267 107.741 5.33348 108.509 5.6551C109.278 5.97671 109.93 6.43234 110.466 7.02197C111.073 7.6652 111.538 8.45137 111.86 9.38049C112.181 10.3096 112.342 11.337 112.342 12.4627C112.342 13.5883 112.181 14.6157 111.86 15.5448C111.538 16.4739 111.073 17.2601 110.466 17.9033C109.93 18.5108 109.278 18.9754 108.509 19.297C107.741 19.6008 106.892 19.7526 105.963 19.7526ZM101.621 12.4627C101.621 13.6955 101.925 14.6961 102.533 15.4644C103.23 16.3042 104.185 16.7241 105.4 16.7241C106.026 16.7241 106.58 16.6169 107.062 16.4025C107.545 16.1702 107.947 15.8575 108.268 15.4644C108.894 14.6961 109.206 13.6955 109.206 12.4627C109.206 11.2119 108.894 10.2203 108.268 9.4877C107.947 9.07674 107.545 8.76406 107.062 8.54965C106.58 8.31737 106.026 8.20123 105.4 8.20123C104.168 8.20123 103.212 8.63005 102.533 9.4877C101.925 10.2381 101.621 11.2298 101.621 12.4627Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M93.5555 19.297C92.1619 19.297 91.0898 18.9039 90.3394 18.1177C89.6068 17.3316 89.2405 16.2863 89.2405 14.982V8.81765C89.2405 8.63897 89.1512 8.54964 88.9725 8.54964H86.2924C86.1494 8.54964 86.0244 8.49603 85.9172 8.38883C85.81 8.28162 85.7563 8.15655 85.7563 8.01361V6.16431C85.7563 6.02137 85.81 5.8963 85.9172 5.78909C86.0244 5.68189 86.1494 5.62828 86.2924 5.62828H88.9725C89.1512 5.62828 89.2405 5.53895 89.2405 5.36027V1.47407C89.2405 1.33113 89.2941 1.20605 89.4013 1.09885C89.5085 0.991643 89.6336 0.93804 89.7766 0.93804H91.7598C91.9028 0.93804 92.0279 0.991643 92.1351 1.09885C92.2423 1.20605 92.2959 1.33113 92.2959 1.47407V5.36027C92.2959 5.53895 92.3852 5.62828 92.5639 5.62828H96.2625C96.4054 5.62828 96.5305 5.68189 96.6377 5.78909C96.7449 5.8963 96.7985 6.02137 96.7985 6.16431V8.01361C96.7985 8.15655 96.7449 8.28162 96.6377 8.38883C96.5305 8.49603 96.4054 8.54964 96.2625 8.54964H92.5639C92.3852 8.54964 92.2959 8.63897 92.2959 8.81765V14.5531C92.2959 15.107 92.4477 15.5269 92.7515 15.8128C93.0552 16.0987 93.4751 16.2416 94.0112 16.2416H96.2625C96.4054 16.2416 96.5305 16.2952 96.6377 16.4024C96.7449 16.5096 96.7985 16.6347 96.7985 16.7777V18.761C96.7985 18.9039 96.7449 19.029 96.6377 19.1362C96.5305 19.2434 96.4054 19.297 96.2625 19.297H93.5555Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M78.7594 19.7526C76.6332 19.7526 74.9804 19.0915 73.8012 17.7693C72.6398 16.4471 72.0591 14.6782 72.0591 12.4626C72.0591 11.0154 72.3182 9.74677 72.8363 8.65685C73.3545 7.54906 74.1139 6.69141 75.1144 6.08391C76.115 5.47642 77.3032 5.17267 78.679 5.17267C80.698 5.17267 82.2883 5.7623 83.4496 6.94156C84.6289 8.12082 85.2185 9.74677 85.2185 11.8194V12.7843C85.2185 13.0165 85.156 13.1952 85.0309 13.3203C84.9059 13.4454 84.7361 13.5079 84.5217 13.5079H75.6237C75.445 13.5079 75.3556 13.6062 75.3556 13.8027C75.3914 14.839 75.7309 15.6609 76.3741 16.2684C77.0173 16.8581 77.8124 17.1529 78.7594 17.1529C80.0816 17.1529 81.0286 16.6526 81.6004 15.652C81.7969 15.3304 82.0202 15.1696 82.2704 15.1696H84.3341C84.4949 15.1696 84.6289 15.2321 84.7361 15.3572C84.8433 15.4644 84.8701 15.6163 84.8165 15.8128C84.477 16.9921 83.7802 17.948 82.726 18.6806C81.6718 19.3953 80.3496 19.7526 78.7594 19.7526ZM75.3288 10.801C75.3288 10.9796 75.4271 11.069 75.6237 11.069H81.8416C82.0024 11.069 82.0828 10.9796 82.0828 10.801C82.0828 9.92545 81.7701 9.19288 81.1447 8.60325C80.5372 7.99575 79.7153 7.692 78.679 7.692C77.7142 7.692 76.9191 7.99575 76.2937 8.60325C75.6862 9.19288 75.3646 9.92545 75.3288 10.801Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M58.246 19.297C58.103 19.297 57.978 19.2434 57.8708 19.1362C57.7636 19.029 57.71 18.9039 57.71 18.761V6.16432C57.71 6.02138 57.7636 5.8963 57.8708 5.7891C57.978 5.68189 58.103 5.62829 58.246 5.62829H59.6665C59.8273 5.62829 59.9523 5.66402 60.0417 5.7355C60.131 5.7891 60.2382 5.88737 60.3633 6.03031C60.4526 6.15538 60.533 6.25366 60.6045 6.32513C60.6938 6.37873 60.81 6.40553 60.9529 6.40553C61.078 6.40553 61.2031 6.3698 61.3281 6.29833C61.4532 6.22685 61.5783 6.14645 61.7034 6.05711C62.0964 5.7891 62.4985 5.57469 62.9094 5.41388C63.3382 5.25307 63.9279 5.17267 64.6783 5.17267C65.7146 5.17267 66.6348 5.39601 67.4388 5.8427C68.2429 6.28939 68.8682 6.94156 69.3149 7.7992C69.7795 8.65685 70.0118 9.68424 70.0118 10.8814V18.761C70.0118 18.9039 69.9582 19.029 69.851 19.1362C69.7438 19.2434 69.6187 19.297 69.4757 19.297H67.4924C67.3495 19.297 67.2244 19.2434 67.1172 19.1362C67.01 19.029 66.9564 18.9039 66.9564 18.761V11.3638C66.9564 10.3632 66.6705 9.58596 66.0988 9.03207C65.5449 8.47817 64.8034 8.20122 63.8743 8.20122C62.9452 8.20122 62.1947 8.47817 61.623 9.03207C61.0512 9.58596 60.7653 10.3632 60.7653 11.3638V18.761C60.7653 18.9039 60.7117 19.029 60.6045 19.1362C60.4973 19.2434 60.3722 19.297 60.2293 19.297H58.246Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M53.1744 19.297C52.8528 19.297 52.5669 19.1541 52.3168 18.8682L48.0822 13.8295C48.0107 13.7581 47.9303 13.7223 47.8409 13.7223C47.7695 13.7223 47.7069 13.7491 47.6533 13.8027C47.5997 13.8563 47.5729 13.9189 47.5729 13.9903V18.761C47.5729 18.9039 47.5193 19.029 47.4121 19.1362C47.3049 19.2434 47.1798 19.297 47.0369 19.297H45.0536C44.9107 19.297 44.7856 19.2434 44.6784 19.1362C44.5712 19.029 44.5176 18.9039 44.5176 18.761V0.536027C44.5176 0.393087 44.5712 0.268014 44.6784 0.160808C44.7856 0.0536026 44.9107 0 45.0536 0H47.0369C47.1798 0 47.3049 0.0536026 47.4121 0.160808C47.5193 0.268014 47.5729 0.393087 47.5729 0.536027V10.5061C47.5729 10.5955 47.5997 10.667 47.6533 10.7206C47.7069 10.7742 47.7695 10.801 47.8409 10.801C47.9124 10.801 47.9839 10.7652 48.0554 10.6938L52.424 6.00351C52.6741 5.75337 52.9421 5.62829 53.228 5.62829H55.3989C55.5597 5.62829 55.6848 5.6819 55.7741 5.7891C55.8813 5.89631 55.9349 6.02138 55.9349 6.16432C55.9349 6.32513 55.8813 6.45914 55.7741 6.56634L50.8695 11.8462C50.7623 11.9534 50.7087 12.0606 50.7087 12.1678C50.7087 12.275 50.7623 12.3822 50.8695 12.4895L55.8813 18.359C55.9885 18.484 56.0421 18.618 56.0421 18.761C56.0421 18.9218 55.9885 19.0558 55.8813 19.163C55.792 19.2523 55.6669 19.297 55.5061 19.297H53.1744Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M34.9505 19.297C34.8076 19.297 34.6736 19.2434 34.5485 19.1362C34.4413 19.029 34.3877 18.9039 34.3877 18.761V6.16432C34.3877 6.02138 34.4413 5.8963 34.5485 5.7891C34.6736 5.68189 34.8076 5.62829 34.9505 5.62829H36.3174C36.639 5.62829 36.8356 5.80697 36.907 6.16432C36.9785 6.48594 37.05 6.72715 37.1214 6.88796C37.2108 7.0309 37.3448 7.10237 37.5235 7.10237C37.6307 7.10237 37.7468 7.06663 37.8719 6.99516C37.9969 6.90582 38.122 6.80755 38.2471 6.70035C38.6402 6.36086 39.0422 6.10178 39.4531 5.92311C39.8641 5.72656 40.4359 5.62829 41.1684 5.62829H42.5353C42.6782 5.62829 42.8033 5.68189 42.9105 5.7891C43.0177 5.8963 43.0713 6.02138 43.0713 6.16432V8.28163C43.0713 8.42457 43.0177 8.54964 42.9105 8.65685C42.8033 8.76405 42.6782 8.81766 42.5353 8.81766H41.0076C39.9713 8.81766 39.1137 9.12141 38.4347 9.7289C37.7736 10.3364 37.443 11.2745 37.443 12.543V18.761C37.443 18.9039 37.3894 19.029 37.2822 19.1362C37.175 19.2434 37.05 19.297 36.907 19.297H34.9505Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M23.8885 19.7526C22.9594 19.7526 22.1107 19.6007 21.3424 19.297C20.5741 18.9754 19.9219 18.5108 19.3859 17.9033C18.7784 17.2601 18.3138 16.4739 17.9922 15.5448C17.6706 14.6157 17.5098 13.5883 17.5098 12.4626C17.5098 11.337 17.6706 10.3096 17.9922 9.38048C18.3138 8.45137 18.7784 7.6652 19.3859 7.02196C19.9219 6.43233 20.5741 5.97671 21.3424 5.65509C22.1107 5.33348 22.9594 5.17267 23.8885 5.17267C24.6389 5.17267 25.2375 5.262 25.6842 5.44068C26.1309 5.60149 26.5775 5.82483 27.0242 6.11072C27.3995 6.36086 27.6943 6.48594 27.9087 6.48594C28.0695 6.48594 28.1946 6.4502 28.2839 6.37873C28.3911 6.30726 28.4983 6.20005 28.6055 6.05711C28.7842 5.77123 28.9986 5.62829 29.2487 5.62829H30.6424C30.7854 5.62829 30.9104 5.68189 31.0176 5.7891C31.1248 5.8963 31.1784 6.02138 31.1784 6.16432V18.761C31.1784 18.9039 31.1248 19.029 31.0176 19.1362C30.9104 19.2434 30.7854 19.297 30.6424 19.297H29.2487C28.9986 19.297 28.7842 19.1541 28.6055 18.8682C28.4983 18.7252 28.3911 18.618 28.2839 18.5466C28.1946 18.4751 28.0695 18.4394 27.9087 18.4394C27.6943 18.4394 27.3995 18.5644 27.0242 18.8146C26.5775 19.1005 26.1309 19.3238 25.6842 19.4846C25.2375 19.6633 24.6389 19.7526 23.8885 19.7526ZM20.6455 12.4626C20.6455 13.6955 20.9582 14.6961 21.5836 15.4644C21.9052 15.8575 22.3072 16.1702 22.7896 16.4024C23.2721 16.6169 23.8259 16.7241 24.4513 16.7241C25.0767 16.7241 25.6306 16.6169 26.113 16.4024C26.5954 16.1702 26.9974 15.8575 27.3191 15.4644C27.9265 14.6961 28.2303 13.6955 28.2303 12.4626C28.2303 11.2298 27.9265 10.2381 27.3191 9.48769C26.9974 9.07674 26.5954 8.76405 26.113 8.54964C25.6306 8.31736 25.0767 8.20122 24.4513 8.20122C23.8259 8.20122 23.2721 8.31736 22.7896 8.54964C22.3072 8.76405 21.9052 9.07674 21.5836 9.48769C20.9582 10.2203 20.6455 11.2119 20.6455 12.4626Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M0.536027 19.297C0.393086 19.297 0.268013 19.2434 0.160808 19.1362C0.0536025 19.029 0 18.9039 0 18.761V1.07206C0 0.929116 0.0536025 0.804043 0.160808 0.696837C0.268013 0.589631 0.393086 0.536028 0.536027 0.536028H6.83434C9.47874 0.536028 11.6407 1.349 13.3203 2.97495C14.16 3.79686 14.8033 4.80638 15.25 6.00351C15.6966 7.18277 15.92 8.48711 15.92 9.91652C15.92 11.3459 15.6966 12.6592 15.25 13.8563C14.8033 15.0356 14.16 16.0362 13.3203 16.8581C11.6407 18.484 9.47874 19.297 6.83434 19.297H0.536027ZM3.24296 15.786C3.24296 15.9647 3.3323 16.054 3.51098 16.054H6.83434C8.53176 16.054 9.88076 15.5537 10.8813 14.5532C11.4352 14.0171 11.8551 13.356 12.141 12.5699C12.4269 11.7837 12.5698 10.8992 12.5698 9.91652C12.5698 8.9338 12.4269 8.04935 12.141 7.26318C11.8551 6.477 11.4352 5.8159 10.8813 5.27988C9.84502 4.27929 8.49602 3.779 6.83434 3.779H3.51098C3.3323 3.779 3.24296 3.86833 3.24296 4.04701V15.786Z"
|
||||
fill="currentColor"/>
|
||||
<g clip-path="url(#clip0_4498_21233)">
|
||||
<path d="M140.006 0.211082H142.457C143.062 0.211082 143.553 0.705733 143.553 1.31591V4.01815C143.553 4.62833 143.062 5.12298 142.457 5.12298H140.006C139.401 5.12298 138.911 4.62833 138.911 4.01815V1.31591C138.911 0.705733 139.401 0.211082 140.006 0.211082Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M116.73 0.211082H119.181C119.786 0.211082 120.277 0.705733 120.277 1.31591V4.01815C120.277 4.62833 119.786 5.12298 119.181 5.12298H116.73C116.125 5.12298 115.635 4.62833 115.635 4.01815V1.31591C115.635 0.705733 116.125 0.211082 116.73 0.211082Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M116.673 11.4481H119.184C119.499 11.4481 119.702 11.6806 119.754 12.0236L119.983 13.6601C120.017 13.8373 120.106 13.9363 120.256 13.9363H121.576C121.89 13.9363 122.089 14.1527 122.146 14.5118L122.374 15.8974C122.411 16.192 122.612 16.3716 122.922 16.4498C122.922 16.4498 124.223 16.8572 129.643 16.8572V20.3121C124.225 20.3121 121.119 19.9024 121.119 19.9024C120.861 19.8633 120.549 19.6446 120.549 19.327L120.32 18.1071C120.32 17.9551 120.197 17.8309 120.046 17.8309H118.49C118.175 17.8309 118.002 17.5754 117.919 17.2554L117.691 16.3278C117.657 16.1345 117.568 16.0516 117.417 16.0516H116.902C116.587 16.0516 116.331 15.7938 116.331 15.4762L116.103 12.0236C116.103 11.7059 116.358 11.4481 116.673 11.4481Z"
|
||||
fill="currentColor"/>
|
||||
<path d="M136.912 15.8974L137.14 14.5118C137.197 14.1527 137.395 13.9363 137.71 13.9363H139.03C139.18 13.9363 139.269 13.8373 139.303 13.6601L139.532 12.0236C139.584 11.6806 139.787 11.4481 140.102 11.4481H142.613C142.927 11.4481 143.183 11.7059 143.183 12.0236L142.955 15.4762C142.955 15.7938 142.699 16.0516 142.384 16.0516H141.869C141.718 16.0516 141.629 16.1345 141.595 16.3278L141.366 17.2554C141.284 17.5754 141.111 17.8309 140.796 17.8309H139.239C139.089 17.8309 138.966 17.9551 138.966 18.1071L138.737 19.327C138.737 19.6446 138.425 19.8633 138.167 19.9024C138.167 19.9024 135.061 20.3121 129.643 20.3121V16.8572C135.063 16.8572 136.364 16.4498 136.364 16.4498C136.674 16.3716 136.875 16.192 136.912 15.8974Z"
|
||||
fill="currentColor"/>
|
||||
</g>
|
||||
<defs>
|
||||
<clipPath id="clip0_4498_21233">
|
||||
<rect width="27.9181" height="20.101" fill="currentColor" transform="matrix(-1 0 0 1 143.553 0.211082)"/>
|
||||
</clipPath>
|
||||
</defs>
|
||||
</svg>
|
||||
|
Before Width: | Height: | Size: 18 KiB |
1421
pkgs/clan-app/ui/package-lock.json
generated
1421
pkgs/clan-app/ui/package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -48,7 +48,7 @@
|
||||
"jsdom": "^26.1.0",
|
||||
"knip": "^5.61.2",
|
||||
"markdown-to-jsx": "^7.7.10",
|
||||
"playwright": "~1.53.2",
|
||||
"playwright": "~1.52.0",
|
||||
"postcss": "^8.4.38",
|
||||
"postcss-url": "^10.1.3",
|
||||
"prettier": "^3.2.5",
|
||||
@@ -74,8 +74,7 @@
|
||||
"@tanstack/solid-query": "^5.76.0",
|
||||
"solid-js": "^1.9.7",
|
||||
"solid-toast": "^0.5.0",
|
||||
"three": "^0.176.0",
|
||||
"valibot": "^1.1.0"
|
||||
"three": "^0.176.0"
|
||||
},
|
||||
"optionalDependencies": {
|
||||
"@esbuild/darwin-arm64": "^0.25.4",
|
||||
|
||||
8
pkgs/clan-app/ui/src/Form/fields/FormSection.tsx
Normal file
8
pkgs/clan-app/ui/src/Form/fields/FormSection.tsx
Normal file
@@ -0,0 +1,8 @@
|
||||
import { JSX } from "solid-js";
|
||||
|
||||
interface FormSectionProps {
|
||||
children: JSX.Element;
|
||||
}
|
||||
const FormSection = (props: FormSectionProps) => {
|
||||
return <div class="p-2">{props.children}</div>;
|
||||
};
|
||||
57
pkgs/clan-app/ui/src/Form/fields/TextInput.tsx
Normal file
57
pkgs/clan-app/ui/src/Form/fields/TextInput.tsx
Normal file
@@ -0,0 +1,57 @@
|
||||
import { splitProps, type JSX } from "solid-js";
|
||||
import {
|
||||
InputBase,
|
||||
InputError,
|
||||
InputLabel,
|
||||
InputVariant,
|
||||
} from "@/src/components/inputBase";
|
||||
import { FieldLayout } from "./layout";
|
||||
|
||||
interface TextInputProps {
|
||||
// Common
|
||||
error?: string;
|
||||
required?: boolean;
|
||||
disabled?: boolean;
|
||||
// Passed to input
|
||||
value: string;
|
||||
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
|
||||
placeholder?: string;
|
||||
variant?: InputVariant;
|
||||
// Passed to label
|
||||
label: JSX.Element;
|
||||
help?: string;
|
||||
// Passed to layout
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export function TextInput(props: TextInputProps) {
|
||||
const [layoutProps, rest] = splitProps(props, ["class"]);
|
||||
return (
|
||||
<FieldLayout
|
||||
label={
|
||||
<InputLabel
|
||||
class="col-span-2"
|
||||
required={props.required}
|
||||
error={!!props.error}
|
||||
help={props.help}
|
||||
>
|
||||
{props.label}
|
||||
</InputLabel>
|
||||
}
|
||||
field={
|
||||
<InputBase
|
||||
variant={props.variant}
|
||||
error={!!props.error}
|
||||
required={props.required}
|
||||
disabled={props.disabled}
|
||||
placeholder={props.placeholder}
|
||||
class="col-span-10"
|
||||
{...props.inputProps}
|
||||
value={props.value}
|
||||
/>
|
||||
}
|
||||
error={props.error && <InputError error={props.error} />}
|
||||
{...layoutProps}
|
||||
/>
|
||||
);
|
||||
}
|
||||
2
pkgs/clan-app/ui/src/Form/fields/index.ts
Normal file
2
pkgs/clan-app/ui/src/Form/fields/index.ts
Normal file
@@ -0,0 +1,2 @@
|
||||
export * from "./FormSection";
|
||||
export * from "./TextInput";
|
||||
26
pkgs/clan-app/ui/src/Form/fields/layout.tsx
Normal file
26
pkgs/clan-app/ui/src/Form/fields/layout.tsx
Normal file
@@ -0,0 +1,26 @@
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import cx from "classnames";
|
||||
|
||||
interface LayoutProps extends JSX.HTMLAttributes<HTMLDivElement> {
|
||||
field?: JSX.Element;
|
||||
label?: JSX.Element;
|
||||
error?: JSX.Element;
|
||||
}
|
||||
export const FieldLayout = (props: LayoutProps) => {
|
||||
const [intern, divProps] = splitProps(props, [
|
||||
"field",
|
||||
"label",
|
||||
"error",
|
||||
"class",
|
||||
]);
|
||||
return (
|
||||
<div
|
||||
class={cx("grid grid-cols-10 items-center", intern.class)}
|
||||
{...divProps}
|
||||
>
|
||||
<div class="col-span-5 flex items-center">{props.label}</div>
|
||||
<div class="col-span-5">{props.field}</div>
|
||||
{props.error && <span class="col-span-full">{props.error}</span>}
|
||||
</div>
|
||||
);
|
||||
};
|
||||
195
pkgs/clan-app/ui/src/api/index.tsx
Normal file
195
pkgs/clan-app/ui/src/api/index.tsx
Normal file
@@ -0,0 +1,195 @@
|
||||
import { API } from "@/api/API";
|
||||
import { Schema as Inventory } from "@/api/Inventory";
|
||||
import { toast } from "solid-toast";
|
||||
import {
|
||||
ErrorToastComponent,
|
||||
CancelToastComponent,
|
||||
} from "@/src/components/toast";
|
||||
|
||||
type OperationNames = keyof API;
|
||||
type Services = NonNullable<Inventory["services"]>;
|
||||
type ServiceNames = keyof Services;
|
||||
|
||||
export type OperationArgs<T extends OperationNames> = API[T]["arguments"];
|
||||
export type OperationResponse<T extends OperationNames> = API[T]["return"];
|
||||
|
||||
export type ClanServiceInstance<T extends ServiceNames> = NonNullable<
|
||||
Services[T]
|
||||
>[string];
|
||||
|
||||
export type SuccessQuery<T extends OperationNames> = Extract<
|
||||
OperationResponse<T>,
|
||||
{ status: "success" }
|
||||
>;
|
||||
export type SuccessData<T extends OperationNames> = SuccessQuery<T>["data"];
|
||||
|
||||
interface SendHeaderType {
|
||||
logging?: { group_path: string[] };
|
||||
}
|
||||
interface BackendSendType<K extends OperationNames> {
|
||||
body: OperationArgs<K>;
|
||||
header?: SendHeaderType;
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
||||
interface ReceiveHeaderType {}
|
||||
interface BackendReturnType<K extends OperationNames> {
|
||||
body: OperationResponse<K>;
|
||||
header: ReceiveHeaderType;
|
||||
}
|
||||
|
||||
const _callApi = <K extends OperationNames>(
|
||||
method: K,
|
||||
args: OperationArgs<K>,
|
||||
backendOpts?: SendHeaderType,
|
||||
): { promise: Promise<BackendReturnType<K>>; op_key: string } => {
|
||||
// if window[method] does not exist, throw an error
|
||||
if (!(method in window)) {
|
||||
console.error(`Method ${method} not found on window object`);
|
||||
// return a rejected promise
|
||||
return {
|
||||
promise: Promise.resolve({
|
||||
body: {
|
||||
status: "error",
|
||||
errors: [
|
||||
{
|
||||
message: `Method ${method} not found on window object`,
|
||||
code: "method_not_found",
|
||||
},
|
||||
],
|
||||
op_key: "noop",
|
||||
},
|
||||
header: {},
|
||||
}),
|
||||
op_key: "noop",
|
||||
};
|
||||
}
|
||||
|
||||
const message: BackendSendType<OperationNames> = {
|
||||
body: args,
|
||||
header: backendOpts,
|
||||
};
|
||||
|
||||
const promise = (
|
||||
window as unknown as Record<
|
||||
OperationNames,
|
||||
(
|
||||
args: BackendSendType<OperationNames>,
|
||||
) => Promise<BackendReturnType<OperationNames>>
|
||||
>
|
||||
)[method](message) as Promise<BackendReturnType<K>>;
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const op_key = (promise as any)._webviewMessageId as string;
|
||||
|
||||
return { promise, op_key };
|
||||
};
|
||||
|
||||
const handleCancel = async <K extends OperationNames>(
|
||||
ops_key: string,
|
||||
orig_task: Promise<BackendReturnType<K>>,
|
||||
) => {
|
||||
console.log("Canceling operation: ", ops_key);
|
||||
const { promise, op_key } = _callApi("delete_task", { task_id: ops_key });
|
||||
promise.catch((error) => {
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToastComponent
|
||||
t={t}
|
||||
message={"Unexpected error: " + (error?.message || String(error))}
|
||||
/>
|
||||
),
|
||||
{
|
||||
duration: 5000,
|
||||
},
|
||||
);
|
||||
console.error("Unhandled promise rejection in callApi:", error);
|
||||
});
|
||||
const resp = await promise;
|
||||
|
||||
if (resp.body.status === "error") {
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToastComponent
|
||||
t={t}
|
||||
message={"Failed to cancel operation: " + ops_key}
|
||||
/>
|
||||
),
|
||||
{
|
||||
duration: 5000,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
(orig_task as any).cancelled = true;
|
||||
}
|
||||
console.log("Cancel response: ", resp);
|
||||
};
|
||||
|
||||
export const callApi = <K extends OperationNames>(
|
||||
method: K,
|
||||
args: OperationArgs<K>,
|
||||
backendOpts?: SendHeaderType,
|
||||
): { promise: Promise<OperationResponse<K>>; op_key: string } => {
|
||||
console.log("Calling API", method, args, backendOpts);
|
||||
|
||||
const { promise, op_key } = _callApi(method, args, backendOpts);
|
||||
promise.catch((error) => {
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToastComponent
|
||||
t={t}
|
||||
message={"Unexpected error: " + (error?.message || String(error))}
|
||||
/>
|
||||
),
|
||||
{
|
||||
duration: 5000,
|
||||
},
|
||||
);
|
||||
console.error("Unhandled promise rejection in callApi:", error);
|
||||
});
|
||||
|
||||
const toastId = toast.custom(
|
||||
(
|
||||
t, // t is the Toast object, t.id is the id of THIS toast instance
|
||||
) => (
|
||||
<CancelToastComponent
|
||||
t={t}
|
||||
message={"Executing " + method}
|
||||
onCancel={handleCancel.bind(null, op_key, promise)}
|
||||
/>
|
||||
),
|
||||
{
|
||||
duration: Infinity,
|
||||
},
|
||||
);
|
||||
|
||||
const new_promise = promise.then((response) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const cancelled = (promise as any).cancelled;
|
||||
if (cancelled) {
|
||||
console.log("Not printing toast because operation was cancelled");
|
||||
}
|
||||
|
||||
const body = response.body;
|
||||
if (body.status === "error" && !cancelled) {
|
||||
toast.remove(toastId);
|
||||
toast.custom(
|
||||
(t) => (
|
||||
<ErrorToastComponent
|
||||
t={t}
|
||||
message={"Error: " + body.errors[0].message}
|
||||
/>
|
||||
),
|
||||
{
|
||||
duration: Infinity,
|
||||
},
|
||||
);
|
||||
} else {
|
||||
toast.remove(toastId);
|
||||
}
|
||||
return body;
|
||||
});
|
||||
|
||||
return { promise: new_promise, op_key: op_key };
|
||||
};
|
||||
188
pkgs/clan-app/ui/src/api_test.tsx
Normal file
188
pkgs/clan-app/ui/src/api_test.tsx
Normal file
@@ -0,0 +1,188 @@
|
||||
import {
|
||||
createForm,
|
||||
FieldValues,
|
||||
getValues,
|
||||
setValue,
|
||||
SubmitHandler,
|
||||
} from "@modular-forms/solid";
|
||||
import { TextInput } from "@/src/Form/fields/TextInput";
|
||||
import { Button } from "./components/Button/Button";
|
||||
import { callApi } from "./api";
|
||||
import { API } from "@/api/API";
|
||||
import { createSignal, Match, Switch, For, Show } from "solid-js";
|
||||
import { Typography } from "./components/Typography";
|
||||
import { useQuery } from "@tanstack/solid-query";
|
||||
import { makePersisted } from "@solid-primitives/storage";
|
||||
import jsonSchema from "@/api/API.json";
|
||||
|
||||
interface APITesterForm extends FieldValues {
|
||||
endpoint: string;
|
||||
payload: string;
|
||||
}
|
||||
|
||||
const ACTUAL_API_ENDPOINT_NAMES: (keyof API)[] = jsonSchema.required.map(
|
||||
(key) => key as keyof API,
|
||||
);
|
||||
|
||||
export const ApiTester = () => {
|
||||
const [persistedTestData, setPersistedTestData] = makePersisted(
|
||||
createSignal<APITesterForm>(),
|
||||
{
|
||||
name: "_test_data",
|
||||
storage: localStorage,
|
||||
},
|
||||
);
|
||||
|
||||
const [formStore, { Form, Field }] = createForm<APITesterForm>({
|
||||
initialValues: persistedTestData() || { endpoint: "", payload: "" },
|
||||
});
|
||||
|
||||
const [endpointSearchTerm, setEndpointSearchTerm] = createSignal(
|
||||
getValues(formStore).endpoint || "",
|
||||
);
|
||||
const [showSuggestions, setShowSuggestions] = createSignal(false);
|
||||
|
||||
const filteredEndpoints = () => {
|
||||
const term = endpointSearchTerm().toLowerCase();
|
||||
if (!term) return ACTUAL_API_ENDPOINT_NAMES;
|
||||
return ACTUAL_API_ENDPOINT_NAMES.filter((ep) =>
|
||||
ep.toLowerCase().includes(term),
|
||||
);
|
||||
};
|
||||
|
||||
const query = useQuery(() => {
|
||||
const currentEndpoint = getValues(formStore).endpoint;
|
||||
const currentPayload = getValues(formStore).payload;
|
||||
const values = getValues(formStore);
|
||||
|
||||
return {
|
||||
queryKey: ["api-tester", currentEndpoint, currentPayload],
|
||||
queryFn: async () => {
|
||||
return await callApi(
|
||||
values.endpoint as keyof API,
|
||||
JSON.parse(values.payload || "{}"),
|
||||
).promise;
|
||||
},
|
||||
staleTime: Infinity,
|
||||
enabled: false,
|
||||
};
|
||||
});
|
||||
|
||||
const handleSubmit: SubmitHandler<APITesterForm> = (values) => {
|
||||
console.log(values);
|
||||
setPersistedTestData(values);
|
||||
setEndpointSearchTerm(values.endpoint);
|
||||
query.refetch();
|
||||
|
||||
const v = getValues(formStore);
|
||||
console.log(v);
|
||||
};
|
||||
return (
|
||||
<div class="p-2">
|
||||
<h1>API Tester</h1>
|
||||
<Form onSubmit={handleSubmit}>
|
||||
<div class="flex flex-col">
|
||||
<Field name="endpoint">
|
||||
{(field, fieldProps) => (
|
||||
<div class="relative">
|
||||
<TextInput
|
||||
label={"endpoint"}
|
||||
value={field.value || ""}
|
||||
inputProps={{
|
||||
...fieldProps,
|
||||
onInput: (e: Event) => {
|
||||
if (fieldProps.onInput) {
|
||||
(fieldProps.onInput as (ev: Event) => void)(e);
|
||||
}
|
||||
setEndpointSearchTerm(
|
||||
(e.currentTarget as HTMLInputElement).value,
|
||||
);
|
||||
setShowSuggestions(true);
|
||||
},
|
||||
onBlur: (e: FocusEvent) => {
|
||||
if (fieldProps.onBlur) {
|
||||
(fieldProps.onBlur as (ev: FocusEvent) => void)(e);
|
||||
}
|
||||
setTimeout(() => setShowSuggestions(false), 150);
|
||||
},
|
||||
onFocus: (e: FocusEvent) => {
|
||||
setEndpointSearchTerm(field.value || "");
|
||||
setShowSuggestions(true);
|
||||
},
|
||||
onKeyDown: (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") {
|
||||
setShowSuggestions(false);
|
||||
}
|
||||
},
|
||||
}}
|
||||
/>
|
||||
<Show
|
||||
when={showSuggestions() && filteredEndpoints().length > 0}
|
||||
>
|
||||
<ul class="absolute z-10 mt-1 max-h-60 w-full overflow-y-auto rounded border border-gray-300 bg-white shadow-lg">
|
||||
<For each={filteredEndpoints()}>
|
||||
{(ep) => (
|
||||
<li
|
||||
class="cursor-pointer p-2 hover:bg-gray-100"
|
||||
onMouseDown={(e) => {
|
||||
e.preventDefault();
|
||||
setValue(formStore, "endpoint", ep);
|
||||
setEndpointSearchTerm(ep);
|
||||
setShowSuggestions(false);
|
||||
}}
|
||||
>
|
||||
{ep}
|
||||
</li>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</Show>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Field name="payload">
|
||||
{(field, fieldProps) => (
|
||||
<div class="my-2 flex flex-col">
|
||||
<label class="mb-1 font-medium" for="payload-textarea">
|
||||
payload
|
||||
</label>
|
||||
<textarea
|
||||
id="payload-textarea"
|
||||
class="min-h-[120px] resize-y rounded border p-2 text-sm focus:outline-none focus:ring-2 focus:ring-blue-400"
|
||||
placeholder={`{\n "key": "value"\n}`}
|
||||
value={field.value || ""}
|
||||
{...fieldProps}
|
||||
onInput={(e) => {
|
||||
fieldProps.onInput?.(e);
|
||||
}}
|
||||
spellcheck={false}
|
||||
autocomplete="off"
|
||||
autocorrect="off"
|
||||
autocapitalize="off"
|
||||
/>
|
||||
</div>
|
||||
)}
|
||||
</Field>
|
||||
<Button class="m-2" disabled={query.isFetching}>
|
||||
Send
|
||||
</Button>
|
||||
</div>
|
||||
</Form>
|
||||
<div>
|
||||
<Typography hierarchy="title" size="default">
|
||||
Result
|
||||
</Typography>
|
||||
<Switch>
|
||||
<Match when={query.isFetching}>
|
||||
<span>loading ...</span>
|
||||
</Match>
|
||||
<Match when={query.isFetched}>
|
||||
<pre>
|
||||
<code>{JSON.stringify(query.data, null, 2)}</code>
|
||||
</pre>
|
||||
</Match>
|
||||
</Switch>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
16
pkgs/clan-app/ui/src/components/BackButton.tsx
Normal file
16
pkgs/clan-app/ui/src/components/BackButton.tsx
Normal file
@@ -0,0 +1,16 @@
|
||||
import { useNavigate } from "@solidjs/router";
|
||||
import { Button } from "./Button/Button";
|
||||
import Icon from "./icon";
|
||||
|
||||
export const BackButton = () => {
|
||||
const navigate = useNavigate();
|
||||
return (
|
||||
<Button
|
||||
variant="ghost"
|
||||
size="s"
|
||||
class="mr-2"
|
||||
onClick={() => navigate(-1)}
|
||||
startIcon={<Icon icon="CaretLeft" />}
|
||||
></Button>
|
||||
);
|
||||
};
|
||||
55
pkgs/clan-app/ui/src/components/Button/Button-Base.css
Normal file
55
pkgs/clan-app/ui/src/components/Button/Button-Base.css
Normal file
@@ -0,0 +1,55 @@
|
||||
@import "Button-Light.css";
|
||||
@import "Button-Dark.css";
|
||||
@import "Button-Ghost.css";
|
||||
|
||||
.button {
|
||||
@apply inline-flex items-center flex-shrink gap-1 justify-center p-4 font-semibold;
|
||||
letter-spacing: 0.0275rem;
|
||||
}
|
||||
|
||||
/* button SIZES */
|
||||
|
||||
.button--default {
|
||||
padding: theme(padding.2) theme(padding.4);
|
||||
height: theme(height.9);
|
||||
border-radius: theme(borderRadius.DEFAULT);
|
||||
|
||||
&:has(> .button__icon--start):has(> .button__label) {
|
||||
padding-left: theme(padding[2.5]);
|
||||
}
|
||||
|
||||
&:has(> .button__icon--end):has(> .button__label) {
|
||||
padding-right: theme(padding[2.5]);
|
||||
}
|
||||
}
|
||||
|
||||
.button--small {
|
||||
padding: theme(padding[1.5]) theme(padding[3]);
|
||||
height: theme(height.8);
|
||||
border-radius: 3px;
|
||||
|
||||
&:has(> .button__icon--start):has(> .button__label) {
|
||||
padding-left: theme(padding.2);
|
||||
}
|
||||
|
||||
&:has(> .button__label):has(> .button__icon--end) {
|
||||
padding-right: theme(padding.2);
|
||||
}
|
||||
}
|
||||
|
||||
/* button group */
|
||||
|
||||
.button-group .button:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.button-group .button:first-child {
|
||||
border-top-right-radius: 0;
|
||||
border-bottom-right-radius: 0;
|
||||
}
|
||||
|
||||
.button-group .button:last-child {
|
||||
border-top-left-radius: 0;
|
||||
border-bottom-left-radius: 0;
|
||||
}
|
||||
31
pkgs/clan-app/ui/src/components/Button/Button-Dark.css
Normal file
31
pkgs/clan-app/ui/src/components/Button/Button-Dark.css
Normal file
@@ -0,0 +1,31 @@
|
||||
/* button DARK and states */
|
||||
|
||||
.button--dark {
|
||||
@apply border border-solid border-secondary-950 bg-primary-800 text-white;
|
||||
|
||||
box-shadow: inset 1px 1px theme(backgroundColor.secondary.700);
|
||||
|
||||
&:disabled {
|
||||
@apply disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300;
|
||||
}
|
||||
|
||||
& .button__icon {
|
||||
color: theme(textColor.secondary.200);
|
||||
}
|
||||
}
|
||||
|
||||
.button--dark-hover:hover {
|
||||
@apply hover:bg-secondary-900;
|
||||
}
|
||||
|
||||
.button--dark-focus:focus {
|
||||
@apply focus:border-secondary-900;
|
||||
}
|
||||
|
||||
.button--dark-active:active {
|
||||
@apply focus:border-secondary-900;
|
||||
}
|
||||
|
||||
.button--dark-active:active {
|
||||
@apply active:border-secondary-900;
|
||||
}
|
||||
11
pkgs/clan-app/ui/src/components/Button/Button-Ghost.css
Normal file
11
pkgs/clan-app/ui/src/components/Button/Button-Ghost.css
Normal file
@@ -0,0 +1,11 @@
|
||||
.button--ghost-hover:hover {
|
||||
@apply hover:bg-secondary-100 hover:text-secondary-900;
|
||||
}
|
||||
|
||||
.button--ghost-focus:focus {
|
||||
@apply focus:bg-secondary-200 focus:text-secondary-900;
|
||||
}
|
||||
|
||||
.button--ghost-active:active {
|
||||
@apply active:bg-secondary-200 active:text-secondary-900;
|
||||
}
|
||||
37
pkgs/clan-app/ui/src/components/Button/Button-Light.css
Normal file
37
pkgs/clan-app/ui/src/components/Button/Button-Light.css
Normal file
@@ -0,0 +1,37 @@
|
||||
/* button LIGHT and states */
|
||||
|
||||
.button--light {
|
||||
@apply border border-solid border-secondary-400 bg-secondary-100 text-secondary-950;
|
||||
|
||||
box-shadow: inset 1px 1px theme(backgroundColor.white);
|
||||
|
||||
&:disabled {
|
||||
@apply disabled:bg-secondary-50 disabled:text-secondary-200 disabled:border-secondary-700;
|
||||
}
|
||||
|
||||
& .button__icon {
|
||||
color: theme(textColor.secondary.900);
|
||||
}
|
||||
}
|
||||
|
||||
.button--light-hover:hover {
|
||||
@apply hover:bg-secondary-200;
|
||||
}
|
||||
|
||||
.button--light-focus:focus {
|
||||
@apply focus:bg-secondary-200;
|
||||
|
||||
& .button__label {
|
||||
color: theme(textColor.secondary.900) !important;
|
||||
}
|
||||
}
|
||||
|
||||
.button--light-active:active {
|
||||
@apply active:bg-secondary-200 border-secondary-600 active:text-secondary-900;
|
||||
|
||||
box-shadow: inset 2px 2px theme(backgroundColor.secondary.300);
|
||||
|
||||
& .button__label {
|
||||
color: theme(textColor.secondary.900) !important;
|
||||
}
|
||||
}
|
||||
@@ -1,115 +1,96 @@
|
||||
import { splitProps, type JSX, createSignal } from "solid-js";
|
||||
import { splitProps, type JSX } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import { Button as KobalteButton } from "@kobalte/core/button";
|
||||
import { Typography } from "../Typography";
|
||||
|
||||
import "./Button.css";
|
||||
import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||
import { Loader } from "@/src/components/Loader/Loader";
|
||||
import "./Button-Base.css";
|
||||
|
||||
export type Size = "default" | "s";
|
||||
export type Hierarchy = "primary" | "secondary";
|
||||
type Variants = "dark" | "light" | "ghost";
|
||||
type Size = "default" | "s";
|
||||
|
||||
export type Action = () => Promise<void>;
|
||||
const variantColors: (
|
||||
disabled: boolean | undefined,
|
||||
) => Record<Variants, string> = (disabled) => ({
|
||||
dark: cx(
|
||||
"button--dark",
|
||||
!disabled && "button--dark-hover", // Hover state
|
||||
!disabled && "button--dark-focus", // Focus state
|
||||
!disabled && "button--dark-active", // Active state
|
||||
// Disabled
|
||||
"disabled:bg-secondary-200 disabled:text-secondary-700 disabled:border-secondary-300",
|
||||
),
|
||||
light: cx(
|
||||
"button--light",
|
||||
|
||||
export interface ButtonProps
|
||||
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
hierarchy?: Hierarchy;
|
||||
size?: Size;
|
||||
ghost?: boolean;
|
||||
children?: JSX.Element;
|
||||
icon?: IconVariant;
|
||||
startIcon?: IconVariant;
|
||||
endIcon?: IconVariant;
|
||||
class?: string;
|
||||
onAction?: Action;
|
||||
}
|
||||
!disabled && "button--light-hover", // Hover state
|
||||
!disabled && "button--light-focus", // Focus state
|
||||
!disabled && "button--light-active", // Active state
|
||||
),
|
||||
ghost: cx(
|
||||
!disabled && "button--ghost-hover", // Hover state
|
||||
!disabled && "button--ghost-focus", // Focus state
|
||||
!disabled && "button--ghost-active", // Active state
|
||||
),
|
||||
});
|
||||
|
||||
const iconSizes: Record<Size, string> = {
|
||||
default: "1rem",
|
||||
s: "0.8125rem",
|
||||
const sizePaddings: Record<Size, string> = {
|
||||
default: cx("button--default"),
|
||||
s: cx("button button--small"), //cx("rounded-sm py-[0.375rem] px-3"),
|
||||
};
|
||||
|
||||
const sizeFont: Record<Size, string> = {
|
||||
default: cx("text-[0.8125rem]"),
|
||||
s: cx("text-[0.75rem]"),
|
||||
};
|
||||
|
||||
interface ButtonProps extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
variant?: Variants;
|
||||
size?: Size;
|
||||
children?: JSX.Element;
|
||||
startIcon?: JSX.Element;
|
||||
endIcon?: JSX.Element;
|
||||
class?: string;
|
||||
}
|
||||
export const Button = (props: ButtonProps) => {
|
||||
const [local, other] = splitProps(props, [
|
||||
"children",
|
||||
"hierarchy",
|
||||
"variant",
|
||||
"size",
|
||||
"ghost",
|
||||
"icon",
|
||||
"startIcon",
|
||||
"endIcon",
|
||||
"class",
|
||||
"onAction",
|
||||
]);
|
||||
|
||||
const size = local.size || "default";
|
||||
const hierarchy = local.hierarchy || "primary";
|
||||
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
|
||||
const onClick = async () => {
|
||||
if (!local.onAction) {
|
||||
console.error("this should not be possible");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await local.onAction();
|
||||
} catch (error) {
|
||||
console.error("Error while executing action", error);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
const buttonInvertion = (variant: Variants) => {
|
||||
return !(!variant || variant === "ghost" || variant === "light");
|
||||
};
|
||||
|
||||
const iconSize = iconSizes[local.size || "default"];
|
||||
|
||||
return (
|
||||
<KobalteButton
|
||||
<button
|
||||
class={cx(
|
||||
local.class,
|
||||
"button", // default button class
|
||||
size,
|
||||
hierarchy,
|
||||
{
|
||||
icon: local.icon,
|
||||
loading: loading(),
|
||||
ghost: local.ghost,
|
||||
},
|
||||
variantColors(props.disabled)[local.variant || "dark"], // button appereance
|
||||
sizePaddings[local.size || "default"], // button size
|
||||
)}
|
||||
onClick={local.onAction ? onClick : undefined}
|
||||
{...other}
|
||||
>
|
||||
<Loader hierarchy={hierarchy} />
|
||||
|
||||
{local.startIcon && (
|
||||
<Icon icon={local.startIcon} class="icon-start" size={iconSize} />
|
||||
<span class="button__icon--start">{local.startIcon}</span>
|
||||
)}
|
||||
|
||||
{local.icon && !local.children && (
|
||||
<Icon icon={local.icon} class="icon" size={iconSize} />
|
||||
)}
|
||||
|
||||
{local.children && !local.icon && (
|
||||
{local.children && (
|
||||
<Typography
|
||||
class="label"
|
||||
class="button__label"
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size={local.size || "default"}
|
||||
inverted={local.hierarchy === "primary"}
|
||||
weight="bold"
|
||||
color="inherit"
|
||||
inverted={buttonInvertion(local.variant || "dark")}
|
||||
weight="medium"
|
||||
tag="span"
|
||||
>
|
||||
{local.children}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{local.endIcon && (
|
||||
<Icon icon={local.endIcon} class="icon-end" size={iconSize} />
|
||||
)}
|
||||
</KobalteButton>
|
||||
{local.endIcon && <span class="button__icon--end">{local.endIcon}</span>}
|
||||
</button>
|
||||
);
|
||||
};
|
||||
|
||||
@@ -1,15 +0,0 @@
|
||||
hr {
|
||||
@apply border-none outline-none bg-inv-2;
|
||||
|
||||
&.inverted {
|
||||
@apply bg-def-3;
|
||||
}
|
||||
|
||||
&[data-orientation="horizontal"] {
|
||||
@apply w-full h-px;
|
||||
}
|
||||
|
||||
&[data-orientation="vertical"] {
|
||||
@apply h-full w-px;
|
||||
}
|
||||
}
|
||||
@@ -1,18 +0,0 @@
|
||||
import "./Divider.css";
|
||||
import cx from "classnames";
|
||||
import { Separator, SeparatorRootProps } from "@kobalte/core/separator";
|
||||
|
||||
export interface DividerProps extends Pick<SeparatorRootProps, "orientation"> {
|
||||
inverted?: boolean;
|
||||
}
|
||||
|
||||
export const Divider = (props: DividerProps) => {
|
||||
const inverted = props.inverted || false;
|
||||
|
||||
return (
|
||||
<Separator
|
||||
class={cx({ inverted: inverted })}
|
||||
orientation={props.orientation}
|
||||
/>
|
||||
);
|
||||
};
|
||||
@@ -1,74 +0,0 @@
|
||||
import {
|
||||
Checkbox as KCheckbox,
|
||||
CheckboxInputProps as KCheckboxInputProps,
|
||||
CheckboxRootProps as KCheckboxRootProps,
|
||||
} from "@kobalte/core/checkbox";
|
||||
import Icon from "@/src/components/Icon/Icon";
|
||||
|
||||
import cx from "classnames";
|
||||
import { Label } from "./Label";
|
||||
import { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import "./Checkbox.css";
|
||||
import { FieldProps } from "./Field";
|
||||
import { Orienter } from "./Orienter";
|
||||
import { Show } from "solid-js";
|
||||
|
||||
export type CheckboxProps = FieldProps &
|
||||
KCheckboxRootProps & {
|
||||
input?: PolymorphicProps<"input", KCheckboxInputProps<"input">>;
|
||||
};
|
||||
|
||||
export const Checkbox = (props: CheckboxProps) => {
|
||||
const alignment = () =>
|
||||
(props.orientation || "vertical") == "vertical" ? "start" : "center";
|
||||
|
||||
const iconChecked = (
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
const iconUnchecked = (
|
||||
<Icon
|
||||
icon="Close"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
);
|
||||
|
||||
return (
|
||||
<KCheckbox
|
||||
class={cx("form-field", "checkbox", props.size, props.orientation, {
|
||||
inverted: props.inverted,
|
||||
ghost: props.ghost,
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
<Orienter orientation={props.orientation} align={alignment()}>
|
||||
<Label
|
||||
labelComponent={KCheckbox.Label}
|
||||
descriptionComponent={KCheckbox.Description}
|
||||
{...props}
|
||||
/>
|
||||
<KCheckbox.Input {...props.input} />
|
||||
<KCheckbox.Control class="checkbox-control">
|
||||
{props.readOnly && (
|
||||
<Show
|
||||
when={props.checked || props.defaultChecked}
|
||||
fallback={iconUnchecked}
|
||||
>
|
||||
{iconChecked}
|
||||
</Show>
|
||||
)}
|
||||
{!props.readOnly && (
|
||||
<KCheckbox.Indicator>{iconChecked}</KCheckbox.Indicator>
|
||||
)}
|
||||
</KCheckbox.Control>
|
||||
</Orienter>
|
||||
</KCheckbox>
|
||||
);
|
||||
};
|
||||
@@ -1,70 +0,0 @@
|
||||
import "./Fieldset.css";
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import { FieldProps } from "./Field";
|
||||
|
||||
export type FieldsetFieldProps = Pick<
|
||||
FieldProps,
|
||||
"orientation" | "inverted"
|
||||
> & {
|
||||
error?: string;
|
||||
disabled?: boolean;
|
||||
};
|
||||
|
||||
export interface FieldsetProps
|
||||
extends Pick<FieldProps, "orientation" | "inverted"> {
|
||||
legend?: string;
|
||||
disabled?: boolean;
|
||||
error?: string;
|
||||
children: JSX.Element | ((props: FieldsetFieldProps) => JSX.Element);
|
||||
}
|
||||
|
||||
export const Fieldset = (props: FieldsetProps) => {
|
||||
const orientation = () => props.orientation || "vertical";
|
||||
|
||||
const [fieldProps] = splitProps(props, [
|
||||
"orientation",
|
||||
"inverted",
|
||||
"disabled",
|
||||
"error",
|
||||
]);
|
||||
|
||||
return (
|
||||
<fieldset
|
||||
role="group"
|
||||
class={cx({ inverted: props.inverted })}
|
||||
disabled={props.disabled || false}
|
||||
>
|
||||
{props.legend && (
|
||||
<legend>
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size="default"
|
||||
weight="normal"
|
||||
color="tertiary"
|
||||
transform="uppercase"
|
||||
inverted={props.inverted}
|
||||
>
|
||||
{props.legend}
|
||||
</Typography>
|
||||
</legend>
|
||||
)}
|
||||
<div class="fields">{props.children(fieldProps)}</div>
|
||||
{props.error && (
|
||||
<div class="error" role="alert">
|
||||
<Typography
|
||||
hierarchy="body"
|
||||
size="xxs"
|
||||
weight="medium"
|
||||
color="error"
|
||||
inverted={props.inverted}
|
||||
>
|
||||
{props.error}
|
||||
</Typography>
|
||||
</div>
|
||||
)}
|
||||
</fieldset>
|
||||
);
|
||||
};
|
||||
20
pkgs/clan-app/ui/src/components/Helpers/List.tsx
Normal file
20
pkgs/clan-app/ui/src/components/Helpers/List.tsx
Normal file
@@ -0,0 +1,20 @@
|
||||
import { type JSX } from "solid-js";
|
||||
|
||||
type sizes = "small" | "medium" | "large";
|
||||
|
||||
const gapSizes: Record<sizes, string> = {
|
||||
small: "gap-2",
|
||||
medium: "gap-4",
|
||||
large: "gap-6",
|
||||
};
|
||||
|
||||
interface List {
|
||||
children: JSX.Element;
|
||||
gapSize: sizes;
|
||||
}
|
||||
|
||||
export const List = (props: List) => {
|
||||
const { children, gapSize } = props;
|
||||
|
||||
return <ul class={`flex flex-col ${gapSizes[gapSize]}`}> {children}</ul>;
|
||||
};
|
||||
1
pkgs/clan-app/ui/src/components/Helpers/index.tsx
Normal file
1
pkgs/clan-app/ui/src/components/Helpers/index.tsx
Normal file
@@ -0,0 +1 @@
|
||||
export { List } from "./List";
|
||||
@@ -1,90 +0,0 @@
|
||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Component, For } from "solid-js";
|
||||
import { Logo, LogoProps, LogoVariant } from "./Logo";
|
||||
import cx from "classnames";
|
||||
|
||||
const variants: LogoVariant[] = ["Clan", "Darknet"];
|
||||
|
||||
const LogoExamples: Component<LogoProps> = (props) => (
|
||||
<div class="grid grid-cols-6 items-center gap-4">
|
||||
<For each={variants}>
|
||||
{(item) => <Logo {...props} variant={item}/>}
|
||||
</For>
|
||||
</div>
|
||||
);
|
||||
|
||||
const meta: Meta<LogoProps> = {
|
||||
title: "Components/Logo",
|
||||
component: LogoExamples,
|
||||
decorators: [
|
||||
(Story: StoryObj, context: StoryContext<LogoProps>) => (
|
||||
<div class={cx(context.args.inverted || false ? "bg-inv-acc-3" : "")}>
|
||||
<Story />
|
||||
</div>
|
||||
),
|
||||
],
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<LogoProps>;
|
||||
|
||||
export const Default: Story = {};
|
||||
|
||||
export const Primary: Story = {
|
||||
args: {
|
||||
color: "primary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Secondary: Story = {
|
||||
args: {
|
||||
color: "secondary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Tertiary: Story = {
|
||||
args: {
|
||||
color: "tertiary",
|
||||
},
|
||||
};
|
||||
|
||||
export const Quaternary: Story = {
|
||||
args: {
|
||||
color: "quaternary",
|
||||
},
|
||||
};
|
||||
|
||||
export const PrimaryInverted: Story = {
|
||||
args: {
|
||||
...Primary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const SecondaryInverted: Story = {
|
||||
args: {
|
||||
...Secondary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const TertiaryInverted: Story = {
|
||||
args: {
|
||||
...Tertiary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const QuaternaryInverted: Story = {
|
||||
args: {
|
||||
...Quaternary.args,
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
|
||||
export const Inverted: Story = {
|
||||
args: {
|
||||
inverted: true,
|
||||
},
|
||||
};
|
||||
@@ -1,38 +0,0 @@
|
||||
import Clan from "@/logos/clan.svg"
|
||||
import Darknet from "@/logos/darknet.svg"
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import { Color, fgClass } from "@/src/components/colors";
|
||||
import { JSX, splitProps } from "solid-js";
|
||||
import cx from "classnames";
|
||||
|
||||
const logos = {
|
||||
Clan,
|
||||
Darknet
|
||||
}
|
||||
|
||||
export type LogoVariant = keyof typeof logos;
|
||||
|
||||
export interface LogoProps extends JSX.SvgSVGAttributes<SVGElement> {
|
||||
class?: string
|
||||
variant: LogoVariant;
|
||||
color?: Color;
|
||||
inverted?: boolean;
|
||||
}
|
||||
|
||||
export const Logo = (props: LogoProps) => {
|
||||
|
||||
const [local, iconProps] = splitProps(props, [
|
||||
"variant",
|
||||
"color",
|
||||
"class",
|
||||
"inverted",
|
||||
]);
|
||||
|
||||
const Logo = logos[local.variant];
|
||||
return <Dynamic
|
||||
component={Logo}
|
||||
class={cx("icon", local.class, fgClass(local.color, local.inverted), {
|
||||
inverted: local.inverted,
|
||||
})}
|
||||
data-logo-name={local.variant}/>
|
||||
}
|
||||
@@ -1,24 +0,0 @@
|
||||
div.modal-content {
|
||||
@apply max-w-[512px];
|
||||
@apply rounded-md;
|
||||
|
||||
/* todo replace with a theme() color */
|
||||
box-shadow: 0.1875rem 0.1875rem 0 0 rgba(145, 172, 175, 0.32);
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-center;
|
||||
@apply w-full px-2 py-1.5;
|
||||
@apply bg-def-3;
|
||||
@apply border border-def-2 rounded-tl-md rounded-tr-md;
|
||||
@apply border-b-def-3;
|
||||
|
||||
& > .title {
|
||||
@apply mx-auto;
|
||||
}
|
||||
}
|
||||
|
||||
& > div.body {
|
||||
@apply p-6 bg-def-1;
|
||||
@apply border border-def-2 rounded-bl-md rounded-br-md;
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
import { TagProps } from "@/src/components/Tag/Tag";
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { fn } from "storybook/test";
|
||||
import { Modal, ModalContext, ModalProps } from "@/src/components/Modal/Modal";
|
||||
import { Fieldset } from "@/src/components/Form/Fieldset";
|
||||
import { TextInput } from "@/src/components/Form/TextInput";
|
||||
import { TextArea } from "@/src/components/Form/TextArea";
|
||||
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||
import { Button } from "../Button/Button";
|
||||
|
||||
const meta: Meta<ModalProps> = {
|
||||
title: "Components/Modal",
|
||||
component: Modal,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<TagProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: "Example Modal",
|
||||
onClose: fn(),
|
||||
children: ({ close }: ModalContext) => (
|
||||
<form class="flex flex-col gap-5">
|
||||
<Fieldset legend="General">
|
||||
{(props) => (
|
||||
<>
|
||||
<TextInput
|
||||
{...props}
|
||||
label="First Name"
|
||||
size="s"
|
||||
required={true}
|
||||
input={{ placeholder: "Ron" }}
|
||||
/>
|
||||
<TextInput
|
||||
{...props}
|
||||
label="Last Name"
|
||||
size="s"
|
||||
required={true}
|
||||
input={{ placeholder: "Burgundy" }}
|
||||
/>
|
||||
<TextArea
|
||||
{...props}
|
||||
label="Bio"
|
||||
size="s"
|
||||
input={{ placeholder: "Tell us a bit about yourself", rows: 8 }}
|
||||
/>
|
||||
<Checkbox
|
||||
{...props}
|
||||
size="s"
|
||||
label="Accept Terms"
|
||||
required={true}
|
||||
/>
|
||||
</>
|
||||
)}
|
||||
</Fieldset>
|
||||
|
||||
<div class="flex w-full items-center justify-end gap-4">
|
||||
<Button size="s" hierarchy="secondary" onClick={close}>
|
||||
Cancel
|
||||
</Button>
|
||||
<Button size="s" type="submit" hierarchy="primary" onClick={close}>
|
||||
Save
|
||||
</Button>
|
||||
</div>
|
||||
</form>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -1,40 +0,0 @@
|
||||
import { createSignal, JSX } from "solid-js";
|
||||
import { Dialog as KDialog } from "@kobalte/core/dialog";
|
||||
import "./Modal.css";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import Icon from "../Icon/Icon";
|
||||
|
||||
export interface ModalContext {
|
||||
close(): void;
|
||||
}
|
||||
|
||||
export interface ModalProps {
|
||||
id?: string;
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
children: (ctx: ModalContext) => JSX.Element;
|
||||
}
|
||||
|
||||
export const Modal = (props: ModalProps) => {
|
||||
const [open, setOpen] = createSignal(true);
|
||||
|
||||
return (
|
||||
<KDialog id={props.id} open={open()} modal={true}>
|
||||
<KDialog.Portal>
|
||||
<KDialog.Content class="modal-content">
|
||||
<div class="header">
|
||||
<Typography class="title" hierarchy="label" family="mono" size="xs">
|
||||
{props.title}
|
||||
</Typography>
|
||||
<KDialog.CloseButton onClick={() => setOpen(false)}>
|
||||
<Icon icon="Close" size="0.75rem" />
|
||||
</KDialog.CloseButton>
|
||||
</div>
|
||||
<div class="body">
|
||||
{props.children({ close: () => setOpen(false) })}
|
||||
</div>
|
||||
</KDialog.Content>
|
||||
</KDialog.Portal>
|
||||
</KDialog>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,14 @@
|
||||
import { List } from "@/src/components/Helpers";
|
||||
import { SidebarListItem } from "../SidebarListItem";
|
||||
|
||||
export const SidebarFlyout = () => {
|
||||
return (
|
||||
<div class="sidebar__flyout">
|
||||
<div class="sidebar__flyout__inner">
|
||||
<List gapSize="small">
|
||||
<SidebarListItem href="/clans" title="Settings" />
|
||||
</List>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
71
pkgs/clan-app/ui/src/components/Sidebar/SidebarHeader.tsx
Normal file
71
pkgs/clan-app/ui/src/components/Sidebar/SidebarHeader.tsx
Normal file
@@ -0,0 +1,71 @@
|
||||
import { createSignal, Show } from "solid-js";
|
||||
import { Typography } from "@/src/components/Typography";
|
||||
import { SidebarFlyout } from "./SidebarFlyout";
|
||||
import "./css/sidebar.css";
|
||||
import Icon from "../icon";
|
||||
|
||||
interface SidebarProps {
|
||||
clanName: string;
|
||||
showFlyout?: () => boolean;
|
||||
}
|
||||
|
||||
const ClanProfile = (props: SidebarProps) => {
|
||||
return (
|
||||
<div
|
||||
class={`sidebar__profile ${props.showFlyout?.() ? "sidebar__profile--flyout" : ""}`}
|
||||
>
|
||||
<Typography
|
||||
class="sidebar__profile__character"
|
||||
tag="span"
|
||||
hierarchy="title"
|
||||
size="m"
|
||||
weight="bold"
|
||||
color="primary"
|
||||
inverted={true}
|
||||
>
|
||||
{props.clanName.slice(0, 1).toUpperCase()}
|
||||
</Typography>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
const ClanTitle = (props: SidebarProps) => {
|
||||
return (
|
||||
<Typography
|
||||
tag="h3"
|
||||
hierarchy="body"
|
||||
size="default"
|
||||
weight="medium"
|
||||
color="primary"
|
||||
inverted={true}
|
||||
>
|
||||
{props.clanName}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
|
||||
export const SidebarHeader = (props: SidebarProps) => {
|
||||
const [showFlyout, toggleFlyout] = createSignal(false);
|
||||
|
||||
function handleClick() {
|
||||
toggleFlyout(!showFlyout());
|
||||
}
|
||||
|
||||
return (
|
||||
<header class="sidebar__header">
|
||||
<div onClick={handleClick} class="sidebar__header__inner">
|
||||
{/* <ClanProfile clanName={props.clanName} showFlyout={showFlyout} /> */}
|
||||
<div class="w-full pl-1 text-white">
|
||||
<ClanTitle clanName={props.clanName} />
|
||||
</div>
|
||||
<Show
|
||||
when={showFlyout}
|
||||
fallback={<Icon size={12} class="text-white" icon="CaretDown" />}
|
||||
>
|
||||
<Icon size={12} class="text-white" icon="CaretDown" />
|
||||
</Show>
|
||||
</div>
|
||||
{showFlyout() && <SidebarFlyout />}
|
||||
</header>
|
||||
);
|
||||
};
|
||||
30
pkgs/clan-app/ui/src/components/Sidebar/SidebarListItem.tsx
Normal file
30
pkgs/clan-app/ui/src/components/Sidebar/SidebarListItem.tsx
Normal file
@@ -0,0 +1,30 @@
|
||||
import { A } from "@solidjs/router";
|
||||
import { Typography } from "@/src/components/Typography";
|
||||
import "./css/sidebar.css";
|
||||
|
||||
interface SidebarListItem {
|
||||
title: string;
|
||||
href: string;
|
||||
}
|
||||
|
||||
export const SidebarListItem = (props: SidebarListItem) => {
|
||||
const { title, href } = props;
|
||||
|
||||
return (
|
||||
<li class="">
|
||||
<A class="sidebar__list__link" href={href}>
|
||||
<Typography
|
||||
class="sidebar__list__content"
|
||||
tag="span"
|
||||
hierarchy="body"
|
||||
size="xs"
|
||||
weight="normal"
|
||||
color="primary"
|
||||
inverted={true}
|
||||
>
|
||||
{title}
|
||||
</Typography>
|
||||
</A>
|
||||
</li>
|
||||
);
|
||||
};
|
||||
@@ -1,33 +0,0 @@
|
||||
div.sidebar-pane {
|
||||
@apply h-full w-auto max-w-60 border-none;
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-between px-3 py-2 rounded-t-[0.5rem];
|
||||
@apply border-t-[1px] border-t-bg-inv-3
|
||||
border-r-[1px] border-r-bg-inv-3
|
||||
border-b-2 border-b-bg-inv-4
|
||||
border-l-[1px] border-l-bg-inv-3;
|
||||
background: linear-gradient(
|
||||
90deg,
|
||||
theme(colors.bg.inv.3) 0%,
|
||||
theme(colors.bg.inv.4) 100%
|
||||
);
|
||||
}
|
||||
|
||||
& > div.body {
|
||||
@apply flex flex-col gap-4 px-2 pt-4 pb-3 w-full h-full;
|
||||
@apply backdrop-blur-md;
|
||||
@apply rounded-b-[0.5rem]
|
||||
border-r-[1px] border-r-bg-inv-3
|
||||
border-b-2 border-b-bg-inv-4
|
||||
border-l-[1px] border-l-bg-inv-3;
|
||||
|
||||
background:
|
||||
linear-gradient(0deg, rgba(0, 0, 0, 0.2) 0%, rgba(0, 0, 0, 0.2) 100%),
|
||||
linear-gradient(
|
||||
180deg,
|
||||
theme(colors.bg.inv.2) 0%,
|
||||
theme(colors.bg.inv.3) 100%
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,115 +0,0 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import {
|
||||
SidebarPane,
|
||||
SidebarPaneProps,
|
||||
} from "@/src/components/Sidebar/SidebarPane";
|
||||
import { SidebarSection } from "./SidebarSection";
|
||||
import { Divider } from "@/src/components/Divider/Divider";
|
||||
import { TextInput } from "@/src/components/Form/TextInput";
|
||||
import { TextArea } from "@/src/components/Form/TextArea";
|
||||
import { Checkbox } from "@/src/components/Form/Checkbox";
|
||||
import { Combobox } from "../Form/Combobox";
|
||||
|
||||
const meta: Meta<SidebarPaneProps> = {
|
||||
title: "Components/Sidebar/Pane",
|
||||
component: SidebarPane,
|
||||
};
|
||||
|
||||
export default meta;
|
||||
|
||||
type Story = StoryObj<SidebarPaneProps>;
|
||||
|
||||
export const Default: Story = {
|
||||
args: {
|
||||
title: "Neptune",
|
||||
onClose: () => {
|
||||
console.log("closing");
|
||||
},
|
||||
children: (
|
||||
<>
|
||||
<SidebarSection
|
||||
title="General"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => (
|
||||
<form class="flex flex-col gap-3">
|
||||
<TextInput
|
||||
label="First Name"
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{ value: "Ron" }}
|
||||
/>
|
||||
<Divider />
|
||||
<TextInput
|
||||
label="Last Name"
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{ value: "Burgundy" }}
|
||||
/>
|
||||
<Divider />
|
||||
<TextArea
|
||||
label="Bio"
|
||||
size="s"
|
||||
inverted={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
input={{
|
||||
value:
|
||||
"It's actually an optical illusion, it's the pattern on the pants.",
|
||||
rows: 4,
|
||||
}}
|
||||
/>
|
||||
<Divider />
|
||||
<Checkbox
|
||||
size="s"
|
||||
label="Share Profile"
|
||||
required={true}
|
||||
inverted={true}
|
||||
readOnly={!editing}
|
||||
checked={true}
|
||||
orientation="horizontal"
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</SidebarSection>
|
||||
<SidebarSection
|
||||
title="Tags"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => (
|
||||
<form class="flex flex-col gap-3">
|
||||
<Combobox
|
||||
size="s"
|
||||
inverted={true}
|
||||
required={true}
|
||||
readOnly={!editing}
|
||||
orientation="horizontal"
|
||||
multiple={true}
|
||||
options={["All", "Home Server", "Backup", "Random"]}
|
||||
defaultValue={["All", "Home Server", "Backup", "Random"]}
|
||||
/>
|
||||
</form>
|
||||
)}
|
||||
</SidebarSection>
|
||||
<SidebarSection
|
||||
title="Advanced Settings"
|
||||
onSave={async () => {
|
||||
console.log("saving general");
|
||||
}}
|
||||
>
|
||||
{(editing) => <></>}
|
||||
</SidebarSection>
|
||||
</>
|
||||
),
|
||||
},
|
||||
};
|
||||
@@ -1,27 +0,0 @@
|
||||
import { JSX } from "solid-js";
|
||||
import "./SidebarPane.css";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import Icon from "../Icon/Icon";
|
||||
import { Button as KButton } from "@kobalte/core/button";
|
||||
|
||||
export interface SidebarPaneProps {
|
||||
title: string;
|
||||
onClose: () => void;
|
||||
children: JSX.Element;
|
||||
}
|
||||
|
||||
export const SidebarPane = (props: SidebarPaneProps) => {
|
||||
return (
|
||||
<div class="sidebar-pane">
|
||||
<div class="header">
|
||||
<Typography hierarchy="body" size="s" weight="bold" inverted={true}>
|
||||
{props.title}
|
||||
</Typography>
|
||||
<KButton onClick={props.onClose}>
|
||||
<Icon icon="Close" color="primary" size="0.75rem" inverted={true} />
|
||||
</KButton>
|
||||
</div>
|
||||
<div class="body">{props.children}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,15 +0,0 @@
|
||||
div.sidebar-section {
|
||||
@apply flex flex-col gap-2 w-full h-full;
|
||||
|
||||
& > div.header {
|
||||
@apply flex items-center justify-between px-1.5;
|
||||
|
||||
& > div.controls {
|
||||
@apply flex justify-end gap-2;
|
||||
}
|
||||
}
|
||||
|
||||
& > div.content {
|
||||
@apply w-full h-full px-1.5 py-3 rounded-md bg-inv-4;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
import { createSignal, JSX } from "solid-js";
|
||||
import "./SidebarSection.css";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import { Button as KButton } from "@kobalte/core/button";
|
||||
import Icon from "../Icon/Icon";
|
||||
|
||||
export interface SidebarSectionProps {
|
||||
title: string;
|
||||
onSave: () => Promise<void>;
|
||||
children: (editing: boolean) => JSX.Element;
|
||||
}
|
||||
|
||||
export const SidebarSection = (props: SidebarSectionProps) => {
|
||||
const [editing, setEditing] = createSignal(false);
|
||||
|
||||
const save = async () => {
|
||||
// todo how do we surface errors?
|
||||
await props.onSave();
|
||||
setEditing(false);
|
||||
};
|
||||
|
||||
return (
|
||||
<div class="sidebar-section">
|
||||
<div class="header">
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="xs"
|
||||
family="mono"
|
||||
weight="light"
|
||||
transform="uppercase"
|
||||
color="tertiary"
|
||||
inverted={true}
|
||||
>
|
||||
{props.title}
|
||||
</Typography>
|
||||
<div class="controls">
|
||||
{editing() && (
|
||||
<KButton>
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
color="tertiary"
|
||||
size="0.75rem"
|
||||
inverted={true}
|
||||
onClick={save}
|
||||
/>
|
||||
</KButton>
|
||||
)}
|
||||
<KButton onClick={() => setEditing(!editing())}>
|
||||
<Icon
|
||||
icon={editing() ? "Close" : "Edit"}
|
||||
color="tertiary"
|
||||
size="0.75rem"
|
||||
inverted={true}
|
||||
/>
|
||||
</KButton>
|
||||
</div>
|
||||
</div>
|
||||
<div class="content">{props.children(editing())}</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,21 @@
|
||||
.sidebar__flyout {
|
||||
top: 0;
|
||||
position: absolute;
|
||||
z-index: theme(zIndex.30);
|
||||
|
||||
padding: theme(padding[1]);
|
||||
width: 100%;
|
||||
height: auto;
|
||||
}
|
||||
|
||||
.sidebar__flyout__inner {
|
||||
position: relative;
|
||||
width: inherit;
|
||||
height: inherit;
|
||||
|
||||
padding: theme(padding.12) theme(padding.3) theme(padding.3);
|
||||
background-color: var(--clr-bg-inv-4);
|
||||
/* / 0.95); */
|
||||
border: 1px solid var(--clr-border-inv-4);
|
||||
border-radius: theme(borderRadius.lg);
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
.sidebar__header {
|
||||
position: relative;
|
||||
padding: 1px 1px 0;
|
||||
cursor: pointer;
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
background: var(--clr-bg-inv-3);
|
||||
|
||||
border-bottom: 1px solid var(--clr-border-inv-3);
|
||||
border-top-left-radius: theme(borderRadius.xl);
|
||||
border-top-right-radius: theme(borderRadius.xl);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar__header__inner {
|
||||
position: relative;
|
||||
z-index: theme(zIndex.40);
|
||||
display: flex;
|
||||
align-items: center;
|
||||
gap: 0 theme(gap.3);
|
||||
|
||||
padding: theme(padding.3) theme(padding.3);
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
.sidebar__list__link {
|
||||
position: relative;
|
||||
cursor: theme(cursor.pointer);
|
||||
|
||||
&:after {
|
||||
content: "";
|
||||
position: absolute;
|
||||
z-index: theme(zIndex.10);
|
||||
top: 0;
|
||||
left: 0;
|
||||
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
border-radius: theme(borderRadius.md);
|
||||
transform: scale(0.98);
|
||||
transition: transform 0.24s ease-in-out;
|
||||
}
|
||||
|
||||
&:hover:after {
|
||||
background: var(--clr-bg-inv-acc-2);
|
||||
transform: scale(theme(scale.100));
|
||||
transition: transform 0.32s ease-in-out;
|
||||
}
|
||||
|
||||
&:active {
|
||||
transform: scale(0.99);
|
||||
transition: transform 0.12s ease-in-out;
|
||||
}
|
||||
|
||||
&:active:after {
|
||||
background: var(--clr-bg-inv-acc-3);
|
||||
transform: scale(theme(scale.100));
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar__list__link {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
display: block;
|
||||
padding: theme(padding.2) theme(padding.3);
|
||||
}
|
||||
|
||||
.sidebar__list__link.active {
|
||||
&:after {
|
||||
background: var(--clr-bg-inv-acc-3);
|
||||
}
|
||||
}
|
||||
|
||||
.sidebar__list__content {
|
||||
position: relative;
|
||||
z-index: 20;
|
||||
}
|
||||
@@ -0,0 +1,19 @@
|
||||
.sidebar__profile {
|
||||
display: flex;
|
||||
justify-content: center;
|
||||
align-items: center;
|
||||
|
||||
width: theme(width.8);
|
||||
height: theme(height.8);
|
||||
|
||||
background: var(--clr-bg-inv-4);
|
||||
border-radius: 50%;
|
||||
}
|
||||
|
||||
.sidebar__profile--flyout {
|
||||
background: var(--clr-bg-def-2);
|
||||
}
|
||||
|
||||
.sidebar__profile--flyout > .sidebar__profile__character {
|
||||
color: var(--clr-fg-def-1) !important;
|
||||
}
|
||||
32
pkgs/clan-app/ui/src/components/Sidebar/css/sidebar.css
Normal file
32
pkgs/clan-app/ui/src/components/Sidebar/css/sidebar.css
Normal file
@@ -0,0 +1,32 @@
|
||||
/* Sidebar Elements */
|
||||
|
||||
@import "./sidebar-header";
|
||||
@import "./sidebar-flyout";
|
||||
@import "./sidebar-list-item";
|
||||
@import "./sidebar-profile";
|
||||
|
||||
/* Sidebar Structure */
|
||||
|
||||
.sidebar {
|
||||
@apply bg-inv-2 h-full border border-solid border-inv-2 min-w-72 rounded-xl;
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
}
|
||||
|
||||
.sidebar__body {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
gap: theme(padding.2);
|
||||
padding: theme(padding.4) theme(padding.2);
|
||||
}
|
||||
|
||||
.sidebar__section {
|
||||
@apply bg-primary-800/90;
|
||||
|
||||
padding: theme(padding.2);
|
||||
border-radius: theme(borderRadius.md);
|
||||
|
||||
::marker {
|
||||
content: "";
|
||||
}
|
||||
}
|
||||
85
pkgs/clan-app/ui/src/components/Sidebar/index.tsx
Normal file
85
pkgs/clan-app/ui/src/components/Sidebar/index.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { For, type JSX, Show } from "solid-js";
|
||||
import { RouteSectionProps } from "@solidjs/router";
|
||||
import { AppRoute, routes } from "@/src";
|
||||
import { SidebarHeader } from "./SidebarHeader";
|
||||
import { SidebarListItem } from "./SidebarListItem";
|
||||
import { Typography } from "../Typography";
|
||||
import "./css/sidebar.css";
|
||||
import Icon, { IconVariant } from "../icon";
|
||||
import { clanMetaQuery } from "@/src/queries/clan-meta";
|
||||
|
||||
const SidebarSection = (props: {
|
||||
title: string;
|
||||
icon: IconVariant;
|
||||
children: JSX.Element;
|
||||
}) => {
|
||||
const { title, children } = props;
|
||||
|
||||
return (
|
||||
<details class="sidebar__section accordeon" open>
|
||||
<summary style="display: contents;">
|
||||
<div class="accordeon__header">
|
||||
<Typography
|
||||
class="inline-flex w-full gap-2 uppercase !tracking-wider"
|
||||
tag="p"
|
||||
hierarchy="body"
|
||||
size="xxs"
|
||||
weight="normal"
|
||||
color="tertiary"
|
||||
inverted={true}
|
||||
>
|
||||
<Icon class="opacity-90" icon={props.icon} size={13} />
|
||||
{title}
|
||||
<Icon icon="CaretDown" class="ml-auto" size={10} />
|
||||
</Typography>
|
||||
</div>
|
||||
</summary>
|
||||
<div class="accordeon__body">{children}</div>
|
||||
</details>
|
||||
);
|
||||
};
|
||||
|
||||
export const Sidebar = (props: RouteSectionProps) => {
|
||||
const query = clanMetaQuery();
|
||||
|
||||
return (
|
||||
<div class="sidebar">
|
||||
<Show
|
||||
when={query.data}
|
||||
fallback={<SidebarHeader clanName={"Untitled"} />}
|
||||
>
|
||||
{(meta) => <SidebarHeader clanName={meta().name} />}
|
||||
</Show>
|
||||
<div class="sidebar__body max-h-[calc(100vh-4rem)] overflow-scroll">
|
||||
<For each={routes.filter((r) => !r.hidden)}>
|
||||
{(route: AppRoute) => (
|
||||
<Show
|
||||
when={route.children}
|
||||
fallback={
|
||||
<SidebarListItem href={route.path} title={route.label} />
|
||||
}
|
||||
>
|
||||
{(children) => (
|
||||
<SidebarSection
|
||||
title={route.label}
|
||||
icon={route.icon || "Paperclip"}
|
||||
>
|
||||
<ul class="flex flex-col gap-y-0.5">
|
||||
<For each={children().filter((r) => !r.hidden)}>
|
||||
{(child) => (
|
||||
<SidebarListItem
|
||||
href={`${route.path}${child.path}`}
|
||||
title={child.label}
|
||||
/>
|
||||
)}
|
||||
</For>
|
||||
</ul>
|
||||
</SidebarSection>
|
||||
)}
|
||||
</Show>
|
||||
)}
|
||||
</For>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -0,0 +1,23 @@
|
||||
.fnt-clr-primary {
|
||||
color: var(--clr-fg-def-1);
|
||||
}
|
||||
|
||||
.fnt-clr-secondary {
|
||||
color: var(--clr-fg-def-2);
|
||||
}
|
||||
|
||||
.fnt-clr-tertiary {
|
||||
color: var(--clr-fg-def-3);
|
||||
}
|
||||
|
||||
.fnt-clr-primary.fnt-clr--inverted {
|
||||
color: var(--clr-fg-inv-1);
|
||||
}
|
||||
|
||||
.fnt-clr-secondary.fnt-clr--inverted {
|
||||
color: var(--clr-fg-inv-2);
|
||||
}
|
||||
|
||||
.fnt-clr-tertiary.fnt-clr--inverted {
|
||||
color: var(--clr-fg-inv-3);
|
||||
}
|
||||
@@ -0,0 +1,4 @@
|
||||
@import "./typography-label.css";
|
||||
@import "./typography-body.css";
|
||||
@import "./typography-title.css";
|
||||
@import "./typography-headline.css";
|
||||
@@ -0,0 +1,23 @@
|
||||
.fnt-body-default {
|
||||
font-size: 1rem;
|
||||
line-height: 132%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
|
||||
.fnt-body-s {
|
||||
font-size: 0.925rem;
|
||||
line-height: 132%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
|
||||
.fnt-body-xs {
|
||||
font-size: 0.875rem;
|
||||
line-height: 132%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
|
||||
.fnt-body-xxs {
|
||||
font-size: 0.75rem;
|
||||
line-height: 132%;
|
||||
letter-spacing: 0.00688rem;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.fnt-headline-default {
|
||||
font-size: 1.5rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 1%;
|
||||
}
|
||||
|
||||
.fnt-headline-m {
|
||||
font-size: 1.75rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 1%;
|
||||
}
|
||||
|
||||
.fnt-headline-l {
|
||||
font-size: 2rem;
|
||||
line-height: 116%;
|
||||
letter-spacing: 1%;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
.fnt-label-default {
|
||||
font-size: 0.8125rem;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.fnt-label-s {
|
||||
font-size: 0.75rem;
|
||||
line-height: 100%;
|
||||
}
|
||||
|
||||
.fnt-label-xs {
|
||||
font-size: 0.6875rem;
|
||||
line-height: 100%;
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
.fnt-title-default {
|
||||
font-size: 1.125rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
|
||||
.fnt-title-m {
|
||||
font-size: 1.25rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
|
||||
.fnt-title-l {
|
||||
font-size: 1.375rem;
|
||||
line-height: 124%;
|
||||
letter-spacing: 3%;
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
@import "./typography-hierarchy/";
|
||||
@import "./typography-color.css";
|
||||
|
||||
.fnt-weight-normal {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.fnt-weight-medium {
|
||||
font-weight: 500;
|
||||
}
|
||||
|
||||
.fnt-weight-bold {
|
||||
font-weight: 700;
|
||||
}
|
||||
|
||||
.fnt-weight-normal.fnt-clr--inverted {
|
||||
font-weight: 300;
|
||||
}
|
||||
|
||||
.fnt-weight-medium.fnt-clr--inverted {
|
||||
font-weight: 400;
|
||||
}
|
||||
|
||||
.fnt-weight-bold.fnt-clr--inverted {
|
||||
font-weight: 700;
|
||||
}
|
||||
109
pkgs/clan-app/ui/src/components/Typography/index.tsx
Normal file
109
pkgs/clan-app/ui/src/components/Typography/index.tsx
Normal file
@@ -0,0 +1,109 @@
|
||||
import { type JSX } from "solid-js";
|
||||
import { Dynamic } from "solid-js/web";
|
||||
import cx from "classnames";
|
||||
import "./css/typography.css";
|
||||
|
||||
type Hierarchy = "body" | "title" | "headline" | "label";
|
||||
type Color = "primary" | "secondary" | "tertiary";
|
||||
type Weight = "normal" | "medium" | "bold";
|
||||
type Tag = "span" | "p" | "h1" | "h2" | "h3" | "h4" | "div";
|
||||
|
||||
const colorMap: Record<Color, string> = {
|
||||
primary: cx("fnt-clr-primary"),
|
||||
secondary: cx("fnt-clr-secondary"),
|
||||
tertiary: cx("fnt-clr-tertiary"),
|
||||
};
|
||||
|
||||
// type Size = "default" | "xs" | "s" | "m" | "l";
|
||||
interface SizeForHierarchy {
|
||||
label: {
|
||||
default: string;
|
||||
xs: string;
|
||||
s: string;
|
||||
};
|
||||
body: {
|
||||
default: string;
|
||||
xs: string;
|
||||
xxs: string;
|
||||
s: string;
|
||||
};
|
||||
headline: {
|
||||
default: string;
|
||||
m: string;
|
||||
l: string;
|
||||
};
|
||||
title: {
|
||||
default: string;
|
||||
m: string;
|
||||
l: string;
|
||||
};
|
||||
}
|
||||
|
||||
type AllowedSizes<H extends Hierarchy> = keyof SizeForHierarchy[H];
|
||||
|
||||
const sizeHierarchyMap: SizeForHierarchy = {
|
||||
body: {
|
||||
default: cx("fnt-body-default"),
|
||||
s: cx("fnt-body-s"),
|
||||
xs: cx("fnt-body-xs"),
|
||||
xxs: cx("fnt-body-xxs"),
|
||||
},
|
||||
headline: {
|
||||
default: cx("fnt-headline-default"),
|
||||
// xs: cx("fnt-headline-xs"),
|
||||
// s: cx("fnt-headline-s"),
|
||||
m: cx("fnt-headline-m"),
|
||||
l: cx("fnt-headline-l"),
|
||||
},
|
||||
title: {
|
||||
default: cx("fnt-title-default"),
|
||||
// xs: cx("fnt-title-xs"),
|
||||
// s: cx("fnt-title-s"),
|
||||
m: cx("fnt-title-m"),
|
||||
l: cx("fnt-title-l"),
|
||||
},
|
||||
label: {
|
||||
default: cx("fnt-label-default"),
|
||||
s: cx("fnt-label-s"),
|
||||
xs: cx("fnt-label-xs"),
|
||||
},
|
||||
};
|
||||
|
||||
const weightMap: Record<Weight, string> = {
|
||||
normal: cx("fnt-weight-normal"),
|
||||
medium: cx("fnt-weight-medium"),
|
||||
bold: cx("fnt-weight-bold"),
|
||||
};
|
||||
|
||||
interface _TypographyProps<H extends Hierarchy> {
|
||||
hierarchy: H;
|
||||
size: AllowedSizes<H>;
|
||||
children: JSX.Element;
|
||||
weight?: Weight;
|
||||
color?: Color | "inherit";
|
||||
inverted?: boolean;
|
||||
tag?: Tag;
|
||||
class?: string;
|
||||
classList?: Record<string, boolean>;
|
||||
}
|
||||
|
||||
export const Typography = <H extends Hierarchy>(props: _TypographyProps<H>) => {
|
||||
return (
|
||||
<Dynamic
|
||||
component={props.tag || "span"}
|
||||
class={cx(
|
||||
props.color === "inherit" && "text-inherit",
|
||||
props.color !== "inherit" && colorMap[props.color || "primary"],
|
||||
props.inverted && "fnt-clr--inverted",
|
||||
sizeHierarchyMap[props.hierarchy][props.size] as string,
|
||||
weightMap[props.weight || "normal"],
|
||||
props.class,
|
||||
)}
|
||||
classList={props.classList}
|
||||
>
|
||||
{props.children}
|
||||
</Dynamic>
|
||||
);
|
||||
};
|
||||
|
||||
export type TypographyProps = _TypographyProps<Hierarchy>;
|
||||
98
pkgs/clan-app/ui/src/components/icon/index.tsx
Normal file
98
pkgs/clan-app/ui/src/components/icon/index.tsx
Normal file
@@ -0,0 +1,98 @@
|
||||
import { Component, JSX, splitProps } from "solid-js";
|
||||
import ArrowBottom from "@/icons/arrow-bottom.svg";
|
||||
import ArrowLeft from "@/icons/arrow-left.svg";
|
||||
import ArrowRight from "@/icons/arrow-right.svg";
|
||||
import ArrowTop from "@/icons/arrow-top.svg";
|
||||
import Attention from "@/icons/attention.svg";
|
||||
import CaretDown from "@/icons/caret-down.svg";
|
||||
import CaretLeft from "@/icons/caret-left.svg";
|
||||
import CaretRight from "@/icons/caret-right.svg";
|
||||
import CaretUp from "@/icons/caret-up.svg";
|
||||
import Checkmark from "@/icons/checkmark.svg";
|
||||
import ClanIcon from "@/icons/clan-icon.svg";
|
||||
import ClanLogo from "@/icons/clan-logo.svg";
|
||||
import Close from "@/icons/close.svg";
|
||||
import Download from "@/icons/download.svg";
|
||||
import Edit from "@/icons/edit.svg";
|
||||
import Expand from "@/icons/expand.svg";
|
||||
import EyeClose from "@/icons/eye-close.svg";
|
||||
import EyeOpen from "@/icons/eye-open.svg";
|
||||
import Filter from "@/icons/filter.svg";
|
||||
import Flash from "@/icons/flash.svg";
|
||||
import Folder from "@/icons/folder.svg";
|
||||
import Grid from "@/icons/grid.svg";
|
||||
import Info from "@/icons/info.svg";
|
||||
import List from "@/icons/list.svg";
|
||||
import Load from "@/icons/load.svg";
|
||||
import More from "@/icons/more.svg";
|
||||
import Paperclip from "@/icons/paperclip.svg";
|
||||
import Plus from "@/icons/plus.svg";
|
||||
import Reload from "@/icons/reload.svg";
|
||||
import Report from "@/icons/report.svg";
|
||||
import Search from "@/icons/search.svg";
|
||||
import Settings from "@/icons/settings.svg";
|
||||
import Trash from "@/icons/trash.svg";
|
||||
import Update from "@/icons/update.svg";
|
||||
import Warning from "@/icons/warning-filled.svg";
|
||||
|
||||
const icons = {
|
||||
ArrowBottom,
|
||||
ArrowLeft,
|
||||
ArrowRight,
|
||||
ArrowTop,
|
||||
Attention,
|
||||
CaretDown,
|
||||
CaretLeft,
|
||||
CaretRight,
|
||||
CaretUp,
|
||||
Checkmark,
|
||||
ClanIcon,
|
||||
ClanLogo,
|
||||
Close,
|
||||
Download,
|
||||
Edit,
|
||||
Expand,
|
||||
EyeClose,
|
||||
EyeOpen,
|
||||
Filter,
|
||||
Flash,
|
||||
Folder,
|
||||
Grid,
|
||||
Info,
|
||||
List,
|
||||
Load,
|
||||
More,
|
||||
Paperclip,
|
||||
Plus,
|
||||
Reload,
|
||||
Report,
|
||||
Search,
|
||||
Settings,
|
||||
Trash,
|
||||
Update,
|
||||
Warning,
|
||||
};
|
||||
|
||||
export type IconVariant = keyof typeof icons;
|
||||
|
||||
interface IconProps extends JSX.SvgSVGAttributes<SVGElement> {
|
||||
icon: IconVariant;
|
||||
size?: number;
|
||||
}
|
||||
|
||||
const Icon: Component<IconProps> = (props) => {
|
||||
const [local, iconProps] = splitProps(props, ["icon"]);
|
||||
|
||||
const IconComponent = icons[local.icon];
|
||||
return IconComponent ? (
|
||||
<IconComponent
|
||||
width={iconProps.size || 16}
|
||||
height={iconProps.size || 16}
|
||||
viewBox="0 0 48 48"
|
||||
ref={iconProps.ref}
|
||||
{...iconProps}
|
||||
/>
|
||||
) : null;
|
||||
};
|
||||
|
||||
export default Icon;
|
||||
193
pkgs/clan-app/ui/src/components/inputBase/index.tsx
Normal file
193
pkgs/clan-app/ui/src/components/inputBase/index.tsx
Normal file
@@ -0,0 +1,193 @@
|
||||
import cx from "classnames";
|
||||
import { JSX, Ref, Show, splitProps } from "solid-js";
|
||||
import Icon, { IconVariant } from "../icon";
|
||||
import { Typography, TypographyProps } from "../Typography";
|
||||
|
||||
export type InputVariant = "outlined" | "ghost";
|
||||
interface InputBaseProps {
|
||||
variant?: InputVariant;
|
||||
value?: string;
|
||||
inputProps?: JSX.InputHTMLAttributes<HTMLInputElement>;
|
||||
required?: boolean;
|
||||
type?: string;
|
||||
inlineLabel?: JSX.Element;
|
||||
class?: string;
|
||||
placeholder?: string;
|
||||
disabled?: boolean;
|
||||
readonly?: boolean;
|
||||
error?: boolean;
|
||||
icon?: IconVariant;
|
||||
/** Overrides the input element */
|
||||
inputElem?: JSX.Element;
|
||||
divRef?: Ref<HTMLDivElement>;
|
||||
}
|
||||
|
||||
const variantBorder: Record<InputVariant, string> = {
|
||||
outlined: "border border-inv-3",
|
||||
ghost: "",
|
||||
};
|
||||
|
||||
const fgStateClasses = cx("aria-disabled:fg-def-4 aria-readonly:fg-def-3");
|
||||
|
||||
export const InputBase = (props: InputBaseProps) => {
|
||||
const [internal, inputProps] = splitProps(props, ["class", "divRef"]);
|
||||
return (
|
||||
<div
|
||||
// eslint-disable-next-line tailwindcss/no-custom-classname
|
||||
class={cx(
|
||||
// Layout
|
||||
"flex px-2 py-[0.375rem] flex-shrink-0 items-center justify-center gap-2 text-sm leading-6",
|
||||
|
||||
// Background
|
||||
"bg-def-1 hover:bg-def-acc-1",
|
||||
|
||||
// Text
|
||||
"fg-def-1",
|
||||
fgStateClasses,
|
||||
// Border
|
||||
variantBorder[props.variant || "outlined"],
|
||||
"rounded-sm",
|
||||
"hover:border-inv-4",
|
||||
"aria-disabled:border-def-2 aria-disabled:border",
|
||||
// Outline
|
||||
"outline-offset-1 outline-1",
|
||||
"active:outline active:outline-inv-3",
|
||||
"focus-visible:outline-double focus-visible:outline-int-1",
|
||||
|
||||
// Cursor
|
||||
"aria-readonly:cursor-no-drop",
|
||||
props.class,
|
||||
)}
|
||||
classList={{
|
||||
// eslint-disable-next-line tailwindcss/no-custom-classname
|
||||
[cx("!border !border-semantic-1 !outline-semantic-1")]: !!props.error,
|
||||
}}
|
||||
aria-invalid={props.error}
|
||||
aria-disabled={props.disabled}
|
||||
aria-readonly={props.readonly}
|
||||
tabIndex={0}
|
||||
role="textbox"
|
||||
ref={internal.divRef}
|
||||
>
|
||||
{props.icon && (
|
||||
<i
|
||||
class={cx("inline-flex fg-def-2", fgStateClasses)}
|
||||
aria-invalid={props.error}
|
||||
aria-disabled={props.disabled}
|
||||
aria-readonly={props.readonly}
|
||||
>
|
||||
<Icon icon={props.icon} font-size="inherit" color="inherit" />
|
||||
</i>
|
||||
)}
|
||||
<Show when={!props.inputElem} fallback={props.inputElem}>
|
||||
<input
|
||||
tabIndex={-1}
|
||||
class="w-full bg-transparent outline-none"
|
||||
value={props.value}
|
||||
type={props.type ? props.type : "text"}
|
||||
readOnly={props.readonly}
|
||||
placeholder={`${props.placeholder || ""}`}
|
||||
required={props.required}
|
||||
disabled={props.disabled}
|
||||
aria-invalid={props.error}
|
||||
aria-disabled={props.disabled}
|
||||
aria-readonly={props.readonly}
|
||||
{...inputProps}
|
||||
/>
|
||||
</Show>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
interface InputLabelProps extends JSX.LabelHTMLAttributes<HTMLLabelElement> {
|
||||
description?: string;
|
||||
required?: boolean;
|
||||
error?: boolean;
|
||||
help?: string;
|
||||
labelAction?: JSX.Element;
|
||||
}
|
||||
export const InputLabel = (props: InputLabelProps) => {
|
||||
const [labelProps, forwardProps] = splitProps(props, [
|
||||
"class",
|
||||
"labelAction",
|
||||
]);
|
||||
return (
|
||||
<label
|
||||
class={cx("flex items-center gap-1", labelProps.class)}
|
||||
{...forwardProps}
|
||||
>
|
||||
<span class="flex flex-col justify-center">
|
||||
<span>
|
||||
<Typography
|
||||
hierarchy="label"
|
||||
size="default"
|
||||
weight="bold"
|
||||
class="inline-flex gap-1 align-middle !fg-def-1"
|
||||
classList={{
|
||||
[cx("!fg-semantic-info-1")]: !!props.error,
|
||||
}}
|
||||
aria-invalid={props.error}
|
||||
>
|
||||
{props.children}
|
||||
</Typography>
|
||||
{props.required && (
|
||||
<Typography
|
||||
class="inline-flex px-1 align-text-top leading-[0.5] fg-def-4"
|
||||
color="inherit"
|
||||
hierarchy="label"
|
||||
weight="bold"
|
||||
size="xs"
|
||||
>
|
||||
<>*</>
|
||||
</Typography>
|
||||
)}
|
||||
{props.help && (
|
||||
<span
|
||||
class=" inline px-2"
|
||||
data-tip={props.help}
|
||||
style={{
|
||||
"--tooltip-color": "#EFFFFF",
|
||||
"--tooltip-text-color": "#0D1416",
|
||||
"--tooltip-tail": "0.8125rem",
|
||||
}}
|
||||
>
|
||||
<Icon class="inline fg-def-3" icon={"Info"} width={"0.8125rem"} />
|
||||
</span>
|
||||
)}
|
||||
</span>
|
||||
<Typography
|
||||
hierarchy="body"
|
||||
size="xs"
|
||||
weight="normal"
|
||||
color="secondary"
|
||||
>
|
||||
{props.description}
|
||||
</Typography>
|
||||
</span>
|
||||
{props.labelAction}
|
||||
</label>
|
||||
);
|
||||
};
|
||||
|
||||
interface InputErrorProps {
|
||||
error: string;
|
||||
typographyProps?: TypographyProps;
|
||||
}
|
||||
export const InputError = (props: InputErrorProps) => {
|
||||
const [typoClasses, rest] = splitProps(
|
||||
props.typographyProps || { class: "" },
|
||||
["class"],
|
||||
);
|
||||
return (
|
||||
<Typography
|
||||
hierarchy="body"
|
||||
// @ts-expect-error: Dependent type is to complex to check how it is coupled to the override for now
|
||||
size="xxs"
|
||||
weight="medium"
|
||||
class={cx("col-span-full px-1 !fg-semantic-info-4", typoClasses)}
|
||||
{...rest}
|
||||
>
|
||||
{props.error}
|
||||
</Typography>
|
||||
);
|
||||
};
|
||||
301
pkgs/clan-app/ui/src/components/toast/index.tsx
Normal file
301
pkgs/clan-app/ui/src/components/toast/index.tsx
Normal file
@@ -0,0 +1,301 @@
|
||||
import { toast, Toast } from "solid-toast"; // Make sure to import Toast type
|
||||
import { Component, JSX, createSignal, onCleanup } from "solid-js";
|
||||
|
||||
// --- Icon Components ---
|
||||
|
||||
const ErrorIcon: Component = () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" fill="#FF4D4F" />
|
||||
<path
|
||||
d="M12 7V13"
|
||||
stroke="white"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="12" cy="16.5" r="1.5" fill="white" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const InfoIcon: Component = () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
||||
>
|
||||
<circle cx="12" cy="12" r="10" fill="#2196F3" />
|
||||
<path
|
||||
d="M12 11V17"
|
||||
stroke="white"
|
||||
stroke-width="2.5"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="12" cy="8.5" r="1.5" fill="white" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
const WarningIcon: Component = () => (
|
||||
<svg
|
||||
width="20"
|
||||
height="20"
|
||||
viewBox="0 0 24 24"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
style={{ "margin-right": "10px", "flex-shrink": "0" }}
|
||||
>
|
||||
<path d="M12 2L22 21H2L12 2Z" fill="#FFC107" />
|
||||
<path
|
||||
d="M12 9V14"
|
||||
stroke="#424242"
|
||||
stroke-width="2"
|
||||
stroke-linecap="round"
|
||||
stroke-linejoin="round"
|
||||
/>
|
||||
<circle cx="12" cy="16.5" r="1" fill="#424242" />
|
||||
</svg>
|
||||
);
|
||||
|
||||
// --- Base Props and Styles ---
|
||||
|
||||
interface BaseToastProps {
|
||||
t: Toast;
|
||||
message: string;
|
||||
onCancel?: () => void; // Optional custom function on X click
|
||||
}
|
||||
|
||||
const baseToastStyle: JSX.CSSProperties = {
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "space-between", // To push X to the right
|
||||
gap: "10px", // Space between content and close button
|
||||
background: "#FFFFFF",
|
||||
color: "#333333",
|
||||
padding: "12px 16px",
|
||||
"border-radius": "6px",
|
||||
"box-shadow": "0 2px 8px rgba(0, 0, 0, 0.12)",
|
||||
"font-family":
|
||||
'system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif',
|
||||
"font-size": "14px",
|
||||
"line-height": "1.4",
|
||||
"min-width": "280px",
|
||||
"max-width": "450px",
|
||||
};
|
||||
|
||||
const closeButtonStyle: JSX.CSSProperties = {
|
||||
background: "none",
|
||||
border: "none",
|
||||
color: "red", // As per original example's X button
|
||||
"font-size": "1.5em",
|
||||
"font-weight": "bold",
|
||||
cursor: "pointer",
|
||||
padding: "0 0 0 10px", // Space to its left
|
||||
"line-height": "1",
|
||||
"align-self": "center", // Ensure vertical alignment
|
||||
};
|
||||
|
||||
// --- Toast Component Definitions ---
|
||||
|
||||
// Error Toast
|
||||
export const ErrorToastComponent: Component<BaseToastProps> = (props) => {
|
||||
// Local state for click feedback and exit animation
|
||||
let timeoutId: number | undefined;
|
||||
const [clicked, setClicked] = createSignal(false);
|
||||
const [exiting, setExiting] = createSignal(false);
|
||||
|
||||
const handleToastClick = () => {
|
||||
setClicked(true);
|
||||
setTimeout(() => {
|
||||
setExiting(true);
|
||||
timeoutId = window.setTimeout(() => {
|
||||
toast.dismiss(props.t.id);
|
||||
}, 300); // Match exit animation duration
|
||||
}, 100); // Brief color feedback before animating out
|
||||
};
|
||||
|
||||
// Cleanup timeout if unmounted early
|
||||
onCleanup(() => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...baseToastStyle,
|
||||
cursor: "pointer",
|
||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
||||
background: clicked()
|
||||
? "#ffeaea"
|
||||
: exiting()
|
||||
? "#fff"
|
||||
: baseToastStyle.background,
|
||||
opacity: exiting() ? 0 : 1,
|
||||
transform: exiting() ? "translateY(-20px)" : "none",
|
||||
}}
|
||||
onClick={handleToastClick}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
||||
>
|
||||
<ErrorIcon />
|
||||
<span>{props.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
// Info Toast
|
||||
export const CancelToastComponent: Component<BaseToastProps> = (props) => {
|
||||
let timeoutId: number | undefined;
|
||||
const [clicked, setClicked] = createSignal(false);
|
||||
const [exiting, setExiting] = createSignal(false);
|
||||
|
||||
// Cleanup timeout if unmounted early
|
||||
onCleanup(() => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
const handleButtonClick = (e: MouseEvent) => {
|
||||
e.stopPropagation();
|
||||
if (props.onCancel) props.onCancel();
|
||||
toast.dismiss(props.t.id);
|
||||
};
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...baseToastStyle,
|
||||
cursor: "pointer",
|
||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
||||
background: clicked()
|
||||
? "#eaf4ff"
|
||||
: exiting()
|
||||
? "#fff"
|
||||
: baseToastStyle.background,
|
||||
opacity: exiting() ? 0 : 1,
|
||||
transform: exiting() ? "translateY(-20px)" : "none",
|
||||
}}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
||||
>
|
||||
<InfoIcon />
|
||||
<span>{props.message}</span>
|
||||
</div>
|
||||
<button
|
||||
onClick={(e) => {
|
||||
setClicked(true);
|
||||
handleButtonClick(e);
|
||||
}}
|
||||
style={{
|
||||
...closeButtonStyle,
|
||||
color: "#2196F3",
|
||||
"font-size": "1em",
|
||||
"font-weight": "normal",
|
||||
padding: "4px 12px",
|
||||
border: "1px solid #2196F3",
|
||||
"border-radius": "4px",
|
||||
background: clicked() ? "#bbdefb" : "#eaf4ff",
|
||||
cursor: "pointer",
|
||||
transition: "background 0.15s",
|
||||
display: "flex",
|
||||
"align-items": "center",
|
||||
"justify-content": "center",
|
||||
width: "70px",
|
||||
height: "32px",
|
||||
}}
|
||||
aria-label="Cancel"
|
||||
disabled={clicked()}
|
||||
>
|
||||
{clicked() ? (
|
||||
// Simple spinner SVG
|
||||
<svg
|
||||
width="18"
|
||||
height="18"
|
||||
viewBox="0 0 50 50"
|
||||
style={{ display: "block" }}
|
||||
>
|
||||
<circle
|
||||
cx="25"
|
||||
cy="25"
|
||||
r="20"
|
||||
fill="none"
|
||||
stroke="#2196F3"
|
||||
stroke-width="4"
|
||||
stroke-linecap="round"
|
||||
stroke-dasharray="31.415, 31.415"
|
||||
transform="rotate(72 25 25)"
|
||||
>
|
||||
<animateTransform
|
||||
attributeName="transform"
|
||||
type="rotate"
|
||||
from="0 25 25"
|
||||
to="360 25 25"
|
||||
dur="0.8s"
|
||||
repeatCount="indefinite"
|
||||
/>
|
||||
</circle>
|
||||
</svg>
|
||||
) : (
|
||||
"Cancel"
|
||||
)}
|
||||
</button>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
|
||||
// Warning Toast
|
||||
const WarningToastComponent: Component<BaseToastProps> = (props) => {
|
||||
let timeoutId: number | undefined;
|
||||
const [clicked, setClicked] = createSignal(false);
|
||||
const [exiting, setExiting] = createSignal(false);
|
||||
|
||||
const handleToastClick = () => {
|
||||
setClicked(true);
|
||||
setTimeout(() => {
|
||||
setExiting(true);
|
||||
timeoutId = window.setTimeout(() => {
|
||||
toast.dismiss(props.t.id);
|
||||
}, 300);
|
||||
}, 100);
|
||||
};
|
||||
|
||||
// Cleanup timeout if unmounted early
|
||||
onCleanup(() => {
|
||||
if (timeoutId) clearTimeout(timeoutId);
|
||||
});
|
||||
|
||||
return (
|
||||
<div
|
||||
style={{
|
||||
...baseToastStyle,
|
||||
cursor: "pointer",
|
||||
transition: "background 0.15s, opacity 0.3s, transform 0.3s",
|
||||
background: clicked()
|
||||
? "#fff8e1"
|
||||
: exiting()
|
||||
? "#fff"
|
||||
: baseToastStyle.background,
|
||||
opacity: exiting() ? 0 : 1,
|
||||
transform: exiting() ? "translateY(-20px)" : "none",
|
||||
}}
|
||||
onClick={handleToastClick}
|
||||
>
|
||||
<div
|
||||
style={{ display: "flex", "align-items": "center", "flex-grow": "1" }}
|
||||
>
|
||||
<WarningIcon />
|
||||
<span>{props.message}</span>
|
||||
</div>
|
||||
</div>
|
||||
);
|
||||
};
|
||||
@@ -1,5 +1,5 @@
|
||||
import type { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Alert, AlertProps } from "@/src/components/Alert/Alert";
|
||||
import { Alert, AlertProps } from "@/src/components/v2/Alert/Alert";
|
||||
import { expect, fn } from "storybook/test";
|
||||
import { StoryContext } from "@kachurun/storybook-solid-vite";
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import "./Alert.css";
|
||||
import cx from "classnames";
|
||||
import Icon, { IconVariant } from "@/src/components/Icon/Icon";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import { Button } from "@kobalte/core/button";
|
||||
import { Alert as KAlert } from "@kobalte/core/alert";
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
box-shadow: 0.125rem 0.125rem 0 0 theme(colors.bg.inv.acc.3) inset;
|
||||
|
||||
&.ghost {
|
||||
@apply bg-transparent border-transparent shadow-none;
|
||||
@apply bg-transparent border-none shadow-none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
@@ -71,7 +71,7 @@
|
||||
0.125rem 0.125rem 0 0 theme(colors.off.white) inset;
|
||||
|
||||
&.ghost {
|
||||
@apply bg-transparent border-transparent shadow-none;
|
||||
@apply bg-transparent border-none shadow-none;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
115
pkgs/clan-app/ui/src/components/v2/Button/Button.tsx
Normal file
115
pkgs/clan-app/ui/src/components/v2/Button/Button.tsx
Normal file
@@ -0,0 +1,115 @@
|
||||
import { splitProps, type JSX, createSignal } from "solid-js";
|
||||
import cx from "classnames";
|
||||
import { Typography } from "../Typography/Typography";
|
||||
import { Button as KobalteButton } from "@kobalte/core/button";
|
||||
|
||||
import "./Button.css";
|
||||
import Icon, { IconVariant } from "@/src/components/v2/Icon/Icon";
|
||||
import { Loader } from "@/src/components/v2/Loader/Loader";
|
||||
|
||||
export type Size = "default" | "s";
|
||||
export type Hierarchy = "primary" | "secondary";
|
||||
|
||||
export type Action = () => Promise<void>;
|
||||
|
||||
export interface ButtonProps
|
||||
extends JSX.ButtonHTMLAttributes<HTMLButtonElement> {
|
||||
hierarchy?: Hierarchy;
|
||||
size?: Size;
|
||||
ghost?: boolean;
|
||||
children?: JSX.Element;
|
||||
icon?: IconVariant;
|
||||
startIcon?: IconVariant;
|
||||
endIcon?: IconVariant;
|
||||
class?: string;
|
||||
onAction?: Action;
|
||||
}
|
||||
|
||||
const iconSizes: Record<Size, string> = {
|
||||
default: "1rem",
|
||||
s: "0.8125rem",
|
||||
};
|
||||
|
||||
export const Button = (props: ButtonProps) => {
|
||||
const [local, other] = splitProps(props, [
|
||||
"children",
|
||||
"hierarchy",
|
||||
"size",
|
||||
"ghost",
|
||||
"icon",
|
||||
"startIcon",
|
||||
"endIcon",
|
||||
"class",
|
||||
"onAction",
|
||||
]);
|
||||
|
||||
const size = local.size || "default";
|
||||
const hierarchy = local.hierarchy || "primary";
|
||||
|
||||
const [loading, setLoading] = createSignal(false);
|
||||
|
||||
const onClick = async () => {
|
||||
if (!local.onAction) {
|
||||
console.error("this should not be possible");
|
||||
return;
|
||||
}
|
||||
|
||||
setLoading(true);
|
||||
|
||||
try {
|
||||
await local.onAction();
|
||||
} catch (error) {
|
||||
console.error("Error while executing action", error);
|
||||
}
|
||||
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const iconSize = iconSizes[local.size || "default"];
|
||||
|
||||
return (
|
||||
<KobalteButton
|
||||
class={cx(
|
||||
local.class,
|
||||
"button", // default button class
|
||||
size,
|
||||
hierarchy,
|
||||
{
|
||||
icon: local.icon,
|
||||
loading: loading(),
|
||||
ghost: local.ghost,
|
||||
},
|
||||
)}
|
||||
onClick={local.onAction ? onClick : undefined}
|
||||
{...other}
|
||||
>
|
||||
<Loader hierarchy={hierarchy} />
|
||||
|
||||
{local.startIcon && (
|
||||
<Icon icon={local.startIcon} class="icon-start" size={iconSize} />
|
||||
)}
|
||||
|
||||
{local.icon && !local.children && (
|
||||
<Icon icon={local.icon} class="icon" size={iconSize} />
|
||||
)}
|
||||
|
||||
{local.children && !local.icon && (
|
||||
<Typography
|
||||
class="label"
|
||||
hierarchy="label"
|
||||
family="mono"
|
||||
size={local.size || "default"}
|
||||
inverted={local.hierarchy === "primary"}
|
||||
weight="bold"
|
||||
tag="span"
|
||||
>
|
||||
{local.children}
|
||||
</Typography>
|
||||
)}
|
||||
|
||||
{local.endIcon && (
|
||||
<Icon icon={local.endIcon} class="icon-end" size={iconSize} />
|
||||
)}
|
||||
</KobalteButton>
|
||||
);
|
||||
};
|
||||
15
pkgs/clan-app/ui/src/components/v2/Divider/Divider.css
Normal file
15
pkgs/clan-app/ui/src/components/v2/Divider/Divider.css
Normal file
@@ -0,0 +1,15 @@
|
||||
div.divider {
|
||||
@apply bg-inv-2;
|
||||
|
||||
&.inverted {
|
||||
@apply bg-def-3;
|
||||
}
|
||||
|
||||
&.horizontal {
|
||||
@apply w-full h-px;
|
||||
}
|
||||
|
||||
&.vertical {
|
||||
@apply h-full w-px;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
import { Meta, StoryObj } from "@kachurun/storybook-solid";
|
||||
import { Divider, DividerProps } from "@/src/components/Divider/Divider";
|
||||
import { Divider, DividerProps } from "@/src/components/v2/Divider/Divider";
|
||||
|
||||
const meta: Meta<DividerProps> = {
|
||||
title: "Components/Divider",
|
||||
14
pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx
Normal file
14
pkgs/clan-app/ui/src/components/v2/Divider/Divider.tsx
Normal file
@@ -0,0 +1,14 @@
|
||||
import "./Divider.css";
|
||||
import cx from "classnames";
|
||||
|
||||
export interface DividerProps {
|
||||
inverted?: boolean;
|
||||
orientation?: "horizontal" | "vertical";
|
||||
}
|
||||
|
||||
export const Divider = (props: DividerProps) => {
|
||||
const inverted = props.inverted || false;
|
||||
const orientation = () => props.orientation || "horizontal";
|
||||
|
||||
return <div class={cx("divider", orientation(), { inverted: inverted })} />;
|
||||
};
|
||||
@@ -16,10 +16,6 @@ div.form-field {
|
||||
&[data-invalid] {
|
||||
@apply border-semantic-error-4;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply cursor-default bg-inherit border-none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -36,10 +32,6 @@ div.form-field {
|
||||
&[data-disabled] {
|
||||
@apply bg-def-4 border-none;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply bg-inherit;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
import type { Meta, StoryContext, StoryObj } from "@kachurun/storybook-solid";
|
||||
import cx from "classnames";
|
||||
import { Checkbox, CheckboxProps } from "@/src/components/Form/Checkbox";
|
||||
import { Checkbox, CheckboxProps } from "@/src/components/v2/Form/Checkbox";
|
||||
|
||||
const Examples = (props: CheckboxProps) => (
|
||||
<div class="flex flex-col gap-8">
|
||||
@@ -94,15 +94,7 @@ export const Disabled: Story = {
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnlyUnchecked: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
defaultChecked: false,
|
||||
},
|
||||
};
|
||||
|
||||
export const ReadOnlyChecked: Story = {
|
||||
export const ReadOnly: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
47
pkgs/clan-app/ui/src/components/v2/Form/Checkbox.tsx
Normal file
47
pkgs/clan-app/ui/src/components/v2/Form/Checkbox.tsx
Normal file
@@ -0,0 +1,47 @@
|
||||
import {
|
||||
Checkbox as KCheckbox,
|
||||
CheckboxInputProps as KCheckboxInputProps,
|
||||
CheckboxRootProps as KCheckboxRootProps,
|
||||
} from "@kobalte/core/checkbox";
|
||||
import Icon from "@/src/components/v2/Icon/Icon";
|
||||
|
||||
import cx from "classnames";
|
||||
import { Label } from "./Label";
|
||||
import { PolymorphicProps } from "@kobalte/core/polymorphic";
|
||||
import "./Checkbox.css";
|
||||
import { FieldProps } from "./Field";
|
||||
import { Orienter } from "./Orienter";
|
||||
|
||||
export type CheckboxProps = FieldProps &
|
||||
KCheckboxRootProps & {
|
||||
input?: PolymorphicProps<"input", KCheckboxInputProps<"input">>;
|
||||
};
|
||||
|
||||
export const Checkbox = (props: CheckboxProps) => (
|
||||
<KCheckbox
|
||||
class={cx("form-field", "checkbox", props.size, props.orientation, {
|
||||
inverted: props.inverted,
|
||||
ghost: props.ghost,
|
||||
})}
|
||||
{...props}
|
||||
>
|
||||
<Orienter orientation={props.orientation} align={"start"}>
|
||||
<Label
|
||||
labelComponent={KCheckbox.Label}
|
||||
descriptionComponent={KCheckbox.Description}
|
||||
{...props}
|
||||
/>
|
||||
<KCheckbox.Input {...props.input} />
|
||||
<KCheckbox.Control class="checkbox-control">
|
||||
<KCheckbox.Indicator>
|
||||
<Icon
|
||||
icon="Checkmark"
|
||||
inverted={props.inverted}
|
||||
color="secondary"
|
||||
size="100%"
|
||||
/>
|
||||
</KCheckbox.Indicator>
|
||||
</KCheckbox.Control>
|
||||
</Orienter>
|
||||
</KCheckbox>
|
||||
);
|
||||
@@ -1,9 +1,9 @@
|
||||
div.form-field.combobox {
|
||||
div.control {
|
||||
@apply flex flex-col size-full gap-2;
|
||||
@apply flex flex-col w-full gap-2;
|
||||
|
||||
div.selected-options {
|
||||
@apply flex flex-wrap gap-1 size-full min-h-5;
|
||||
@apply flex flex-wrap gap-1 w-full min-h-5;
|
||||
}
|
||||
|
||||
div.input-container {
|
||||
@@ -44,8 +44,7 @@ div.form-field.combobox {
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-none border-none bg-inherit;
|
||||
@apply p-0 resize-none;
|
||||
@apply outline-def-2 cursor-not-allowed;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -77,10 +76,6 @@ div.form-field.combobox {
|
||||
& > input {
|
||||
@apply px-1.5 py-1;
|
||||
font-size: 0.75rem;
|
||||
|
||||
&[data-readonly] {
|
||||
@apply p-0;
|
||||
}
|
||||
}
|
||||
|
||||
& > button.trigger {
|
||||
@@ -116,10 +111,6 @@ div.form-field.combobox {
|
||||
&[data-invalid] {
|
||||
@apply outline-semantic-error-4;
|
||||
}
|
||||
|
||||
&[data-readonly] {
|
||||
@apply outline-none border-none bg-inherit cursor-auto;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -124,7 +124,6 @@ export const ReadOnly: Story = {
|
||||
args: {
|
||||
...Tooltip.args,
|
||||
readOnly: true,
|
||||
defaultValue: "foo",
|
||||
},
|
||||
};
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
import Icon from "@/src/components/Icon/Icon";
|
||||
import Icon from "@/src/components/v2/Icon/Icon";
|
||||
import {
|
||||
Combobox as KCombobox,
|
||||
ComboboxRootOptions as KComboboxRootOptions,
|
||||
@@ -11,9 +11,9 @@ import { Label } from "./Label";
|
||||
import cx from "classnames";
|
||||
import { FieldProps } from "./Field";
|
||||
import { Orienter } from "./Orienter";
|
||||
import { Typography } from "@/src/components/Typography/Typography";
|
||||
import { Typography } from "@/src/components/v2/Typography/Typography";
|
||||
import { Accessor, Component, For, Show, splitProps } from "solid-js";
|
||||
import { Tag } from "@/src/components/Tag/Tag";
|
||||
import { Tag } from "@/src/components/v2/Tag/Tag";
|
||||
|
||||
export type ComboboxProps<Option, OptGroup = never> = FieldProps &
|
||||
KComboboxRootOptions<Option, OptGroup> & {
|
||||
@@ -83,18 +83,14 @@ export const DefaultItemControl = <Option,>(
|
||||
</For>
|
||||
</div>
|
||||
</Show>
|
||||
{!(props.readOnly && props.multiple) && (
|
||||
<div class="input-container">
|
||||
<KCombobox.Input />
|
||||
{!props.readOnly && (
|
||||
<KCombobox.Trigger class="trigger">
|
||||
<KCombobox.Icon class="icon">
|
||||
<Icon icon="Expand" inverted={props.inverted} size="100%" />
|
||||
</KCombobox.Icon>
|
||||
</KCombobox.Trigger>
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
<div class="input-container">
|
||||
<KCombobox.Input />
|
||||
<KCombobox.Trigger class="trigger">
|
||||
<KCombobox.Icon class="icon">
|
||||
<Icon icon="Expand" inverted={props.inverted} size="100%" />
|
||||
</KCombobox.Icon>
|
||||
</KCombobox.Trigger>
|
||||
</div>
|
||||
</>
|
||||
);
|
||||
|
||||
@@ -105,13 +101,7 @@ export const Combobox = <Option, OptGroup = never>(
|
||||
const itemControl = () => props.itemControl || DefaultItemControl;
|
||||
const itemComponent = () => props.itemComponent || DefaultItemComponent;
|
||||
|
||||
const align = () => {
|
||||
if (props.readOnly) {
|
||||
return "center";
|
||||
} else {
|
||||
return props.orientation === "horizontal" ? "start" : "center";
|
||||
}
|
||||
};
|
||||
const align = () => (props.orientation === "horizontal" ? "start" : "center");
|
||||
|
||||
return (
|
||||
<KCombobox
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user