Dev/v1.2 (#4)
Co-authored-by: ISMAIL MASSERAN <topaz@Mac.dlinkrouter.local> Reviewed-on: #4
@@ -77,3 +77,6 @@ SELECT setval('public.permissions_id_seq', COALESCE((SELECT MAX(id) FROM permiss
|
|||||||
```bash
|
```bash
|
||||||
git reset --hard development # moves the current branch (e.g., main) to point exactly where development
|
git reset --hard development # moves the current branch (e.g., main) to point exactly where development
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Public endpoint to apply membership:
|
||||||
|
## http://localhost:5173/apply-membership
|
||||||
@@ -16,5 +16,16 @@
|
|||||||
[ ] jana surat lepas lulus anggota
|
[ ] jana surat lepas lulus anggota
|
||||||
|
|
||||||
## Present to Boss (2/7/2026)
|
## Present to Boss (2/7/2026)
|
||||||
[ ] discuss logo baru MyKOPKB
|
[x] discuss logo baru MyKOPKB
|
||||||
[ ] layout footer untuk surat rasmi
|
[x] layout footer untuk surat rasmi
|
||||||
|
[ ] maklumat pekerjaan kena buat dropdown untuk nama majikan
|
||||||
|
[ ] tambah minima untuk borang permohonan (tukar jadi caruman saham potongan bulanan)
|
||||||
|
[x] tukar waris ke penama
|
||||||
|
[x] ic dan surat pengesahan majikkan perlu mandatori
|
||||||
|
[ ] semakan lembaga tukar jadi maklumat keputusan
|
||||||
|
[x] boleh attach document untuk peringkat pentadbir
|
||||||
|
[x] surat attach dekat email selepas bos
|
||||||
|
[ ] pembayaran pertama tukar jadi one-off
|
||||||
|
[ ] syer maksima silap tukar jadi rm50.00
|
||||||
|
[ ] letak sign digital
|
||||||
|
[x] running no anggota dalam surat dan masa.
|
||||||
@@ -96,3 +96,6 @@ API_ALLOWED_USER_AGENTS=
|
|||||||
ENABLE_API_KEY_AUTH=true
|
ENABLE_API_KEY_AUTH=true
|
||||||
API_VALID_KEYS=86f58825e5c1e0872d8786092d47e3bd,6461fc5390660c55dc47bdfcd85ed9ae
|
API_VALID_KEYS=86f58825e5c1e0872d8786092d47e3bd,6461fc5390660c55dc47bdfcd85ed9ae
|
||||||
API_LOG_KEY_USAGE=true
|
API_LOG_KEY_USAGE=true
|
||||||
|
|
||||||
|
PUBLIC_PROFILE_TOKEN_TTL_DAYS=7
|
||||||
|
FRONTEND_URL=https://mykopkb.koppkb.com
|
||||||
@@ -103,3 +103,6 @@ API_ALLOWED_USER_AGENTS=
|
|||||||
ENABLE_API_KEY_AUTH=true
|
ENABLE_API_KEY_AUTH=true
|
||||||
API_VALID_KEYS=86f58825e5c1e0872d8786092d47e3bd,6461fc5390660c55dc47bdfcd85ed9ae
|
API_VALID_KEYS=86f58825e5c1e0872d8786092d47e3bd,6461fc5390660c55dc47bdfcd85ed9ae
|
||||||
API_LOG_KEY_USAGE=true
|
API_LOG_KEY_USAGE=true
|
||||||
|
|
||||||
|
PUBLIC_PROFILE_TOKEN_TTL_DAYS=7
|
||||||
|
FRONTEND_URL=https://mykopkb.koppkb.com
|
||||||
@@ -3,17 +3,22 @@
|
|||||||
namespace Modules\ActivityLog\Http\Controllers;
|
namespace Modules\ActivityLog\Http\Controllers;
|
||||||
|
|
||||||
use App\Http\Controllers\Controller;
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Foundation\Auth\Access\AuthorizesRequests;
|
||||||
use Illuminate\Http\Request;
|
use Illuminate\Http\Request;
|
||||||
use Spatie\Activitylog\Models\Activity;
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use App\Services\ActivityLogger;
|
use App\Services\ActivityLogger;
|
||||||
|
|
||||||
class ActivityLogController extends Controller
|
class ActivityLogController extends Controller
|
||||||
{
|
{
|
||||||
|
use AuthorizesRequests;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Display a listing of the resource.
|
* Display a listing of the resource.
|
||||||
*/
|
*/
|
||||||
public function index(Request $request)
|
public function index(Request $request)
|
||||||
{
|
{
|
||||||
|
$this->authorize('viewAny', Activity::class);
|
||||||
|
|
||||||
// ActivityLogger::logView(new Activity, 'Viewed activity logs');
|
// ActivityLogger::logView(new Activity, 'Viewed activity logs');
|
||||||
|
|
||||||
$perPage = $request->get('per_page', 10);
|
$perPage = $request->get('per_page', 10);
|
||||||
@@ -27,8 +32,10 @@ class ActivityLogController extends Controller
|
|||||||
$q->where('description', 'ILIKE', "%{$search}%")
|
$q->where('description', 'ILIKE', "%{$search}%")
|
||||||
->orWhere('subject_type', 'ILIKE', "%{$search}%")
|
->orWhere('subject_type', 'ILIKE', "%{$search}%")
|
||||||
->orWhere('event', 'ILIKE', "%{$search}%")
|
->orWhere('event', 'ILIKE', "%{$search}%")
|
||||||
->orWhere('causer.name', 'ILIKE', "%{$search}%")
|
->orWhereHas('causer', function ($causerQuery) use ($search) {
|
||||||
->orWhere('causer.email', 'ILIKE', "%{$search}%");
|
$causerQuery->where('name', 'ILIKE', "%{$search}%")
|
||||||
|
->orWhere('email', 'ILIKE', "%{$search}%");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,7 +3,10 @@
|
|||||||
namespace Modules\ActivityLog\Providers;
|
namespace Modules\ActivityLog\Providers;
|
||||||
|
|
||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Modules\ActivityLog\Policies\ActivityLogPolicy;
|
||||||
|
use Spatie\Activitylog\Models\Activity;
|
||||||
use Nwidart\Modules\Traits\PathNamespace;
|
use Nwidart\Modules\Traits\PathNamespace;
|
||||||
use RecursiveDirectoryIterator;
|
use RecursiveDirectoryIterator;
|
||||||
use RecursiveIteratorIterator;
|
use RecursiveIteratorIterator;
|
||||||
@@ -21,6 +24,8 @@ class ActivityLogServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
Gate::policy(Activity::class, ActivityLogPolicy::class);
|
||||||
|
|
||||||
$this->registerCommands();
|
$this->registerCommands();
|
||||||
$this->registerCommandSchedules();
|
$this->registerCommandSchedules();
|
||||||
$this->registerTranslations();
|
$this->registerTranslations();
|
||||||
|
|||||||
@@ -8,6 +8,7 @@ use Illuminate\Database\Eloquent\Factories\HasFactory;
|
|||||||
use Illuminate\Database\Eloquent\SoftDeletes;
|
use Illuminate\Database\Eloquent\SoftDeletes;
|
||||||
use Illuminate\Foundation\Auth\User as Authenticatable;
|
use Illuminate\Foundation\Auth\User as Authenticatable;
|
||||||
use Illuminate\Notifications\Notifiable;
|
use Illuminate\Notifications\Notifiable;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
use Laravel\Sanctum\HasApiTokens;
|
use Laravel\Sanctum\HasApiTokens;
|
||||||
use Spatie\Activitylog\LogOptions;
|
use Spatie\Activitylog\LogOptions;
|
||||||
use Spatie\Activitylog\Traits\LogsActivity;
|
use Spatie\Activitylog\Traits\LogsActivity;
|
||||||
@@ -51,9 +52,11 @@ class User extends Authenticatable
|
|||||||
'marriage_status',
|
'marriage_status',
|
||||||
'member_number',
|
'member_number',
|
||||||
'member_type',
|
'member_type',
|
||||||
|
'public_profile_token',
|
||||||
'join_date',
|
'join_date',
|
||||||
'birth_date',
|
'birth_date',
|
||||||
'birth_place',
|
'birth_place',
|
||||||
|
'onboarding_completed_at',
|
||||||
];
|
];
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -67,8 +70,55 @@ class User extends Authenticatable
|
|||||||
'two_factor_secret',
|
'two_factor_secret',
|
||||||
'two_factor_recovery_codes',
|
'two_factor_recovery_codes',
|
||||||
'two_factor_confirmed_at',
|
'two_factor_confirmed_at',
|
||||||
|
'public_profile_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
protected static function booted(): void
|
||||||
|
{
|
||||||
|
static::creating(function (User $user) {
|
||||||
|
$expiryDays = self::publicProfileTokenTtlDays();
|
||||||
|
|
||||||
|
if (empty($user->public_profile_token)) {
|
||||||
|
$user->public_profile_token = Str::random(48);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (empty($user->public_profile_token_expires_at)) {
|
||||||
|
$user->public_profile_token_expires_at = now()->addDays($expiryDays);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public static function publicProfileTokenTtlDays(): int
|
||||||
|
{
|
||||||
|
return (int) config('user.public_profile_token_ttl_days', 90);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function isPublicProfileTokenExpired(): bool
|
||||||
|
{
|
||||||
|
return $this->public_profile_token_expires_at !== null
|
||||||
|
&& $this->public_profile_token_expires_at->isPast();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function ensurePublicProfileToken(): string
|
||||||
|
{
|
||||||
|
$expiryDays = self::publicProfileTokenTtlDays();
|
||||||
|
|
||||||
|
if (blank($this->public_profile_token) || $this->isPublicProfileTokenExpired()) {
|
||||||
|
$this->forceFill([
|
||||||
|
'public_profile_token' => Str::random(48),
|
||||||
|
'public_profile_token_expires_at' => now()->addDays($expiryDays),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
return $this->public_profile_token;
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->forceFill([
|
||||||
|
'public_profile_token_expires_at' => now()->addDays($expiryDays),
|
||||||
|
])->save();
|
||||||
|
|
||||||
|
return $this->public_profile_token;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the attributes that should be cast.
|
* Get the attributes that should be cast.
|
||||||
*
|
*
|
||||||
@@ -84,9 +134,11 @@ class User extends Authenticatable
|
|||||||
'marriage_status' => 'string',
|
'marriage_status' => 'string',
|
||||||
'member_number' => 'integer',
|
'member_number' => 'integer',
|
||||||
'member_type' => 'string',
|
'member_type' => 'string',
|
||||||
|
'public_profile_token_expires_at' => 'datetime',
|
||||||
'join_date' => 'date',
|
'join_date' => 'date',
|
||||||
'birth_date' => 'date',
|
'birth_date' => 'date',
|
||||||
'birth_place' => 'string',
|
'birth_place' => 'string',
|
||||||
|
'onboarding_completed_at' => 'datetime',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ class SessionController extends Controller
|
|||||||
], 401);
|
], 401);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
$user->ensurePublicProfileToken();
|
||||||
$user->load(['roles.permissions']);
|
$user->load(['roles.permissions']);
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
|
|||||||
@@ -12,6 +12,8 @@ class AuthSessionService
|
|||||||
{
|
{
|
||||||
public function createAuthResponse(User $user, string $message, int $status = 200): JsonResponse
|
public function createAuthResponse(User $user, string $message, int $status = 200): JsonResponse
|
||||||
{
|
{
|
||||||
|
$user->ensurePublicProfileToken();
|
||||||
|
|
||||||
if (config('app.env') !== 'local') {
|
if (config('app.env') !== 'local') {
|
||||||
$user->tokens()->delete();
|
$user->tokens()->delete();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -36,9 +36,11 @@ class UserResource extends JsonResource
|
|||||||
'marriage_status' => $this->marriage_status,
|
'marriage_status' => $this->marriage_status,
|
||||||
'member_number' => $this->member_number,
|
'member_number' => $this->member_number,
|
||||||
'member_type' => $this->member_type,
|
'member_type' => $this->member_type,
|
||||||
|
'public_profile_token' => $this->public_profile_token,
|
||||||
'join_date' => $this->join_date,
|
'join_date' => $this->join_date,
|
||||||
'birth_date' => $this->birth_date,
|
'birth_date' => $this->birth_date,
|
||||||
'birth_place' => $this->birth_place,
|
'birth_place' => $this->birth_place,
|
||||||
|
'onboarding_completed_at' => $this->onboarding_completed_at,
|
||||||
'created_at' => $this->created_at,
|
'created_at' => $this->created_at,
|
||||||
'updated_at' => $this->updated_at,
|
'updated_at' => $this->updated_at,
|
||||||
'deleted_at' => $this->deleted_at,
|
'deleted_at' => $this->deleted_at,
|
||||||
|
|||||||
@@ -2,19 +2,28 @@
|
|||||||
|
|
||||||
namespace Modules\MembershipApplication\Emails;
|
namespace Modules\MembershipApplication\Emails;
|
||||||
|
|
||||||
|
use App\Models\Document;
|
||||||
|
use App\Notifications\Concerns\BuildsMailMessage;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\MembershipApplication\Entities\MembershipApplication;
|
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||||
|
|
||||||
class MembershipApplicationFailedNotification extends Notification
|
class MembershipApplicationFailedNotification extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
use BuildsMailMessage;
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected MembershipApplication $application
|
public MembershipApplication $application,
|
||||||
|
public Document $resultLetter,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
return ['mail'];
|
return ['mail'];
|
||||||
@@ -23,13 +32,21 @@ class MembershipApplicationFailedNotification extends Notification
|
|||||||
public function toMail(object $notifiable): MailMessage
|
public function toMail(object $notifiable): MailMessage
|
||||||
{
|
{
|
||||||
$this->application->loadMissing('applicant');
|
$this->application->loadMissing('applicant');
|
||||||
|
$applicant = $this->application->applicant;
|
||||||
|
|
||||||
return (new MailMessage)
|
return $this->mailMessage(
|
||||||
->subject('Keputusan Permohonan Keahlian')
|
subject: 'Keputusan Permohonan Keahlian KOPKB - TIDAK LULUS',
|
||||||
->markdown('membershipapplication::emails.failed', [
|
view: 'membershipapplication::emails.failed',
|
||||||
'name' => $this->application->applicant->name,
|
data: [
|
||||||
'applicationNumber' => $this->application->application_number,
|
'application' => $this->application,
|
||||||
'logoPath' => public_path('images/logo-kopkb.svg'),
|
'applicant' => $applicant,
|
||||||
]);
|
],
|
||||||
|
)->attach(
|
||||||
|
Storage::disk(Document::STORAGE_DISK)->path($this->resultLetter->path),
|
||||||
|
[
|
||||||
|
'as' => $this->resultLetter->name,
|
||||||
|
'mime' => $this->resultLetter->mime_type,
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,19 +2,28 @@
|
|||||||
|
|
||||||
namespace Modules\MembershipApplication\Emails;
|
namespace Modules\MembershipApplication\Emails;
|
||||||
|
|
||||||
|
use App\Models\Document;
|
||||||
|
use App\Notifications\Concerns\BuildsMailMessage;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
|
use Illuminate\Contracts\Queue\ShouldQueue;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
use Modules\MembershipApplication\Entities\MembershipApplication;
|
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||||
|
|
||||||
class MembershipApplicationPassedNotification extends Notification
|
class MembershipApplicationPassedNotification extends Notification implements ShouldQueue
|
||||||
{
|
{
|
||||||
|
use BuildsMailMessage;
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
protected MembershipApplication $application
|
public MembershipApplication $application,
|
||||||
|
public Document $resultLetter,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<int, string>
|
||||||
|
*/
|
||||||
public function via(object $notifiable): array
|
public function via(object $notifiable): array
|
||||||
{
|
{
|
||||||
return ['mail'];
|
return ['mail'];
|
||||||
@@ -23,13 +32,21 @@ class MembershipApplicationPassedNotification extends Notification
|
|||||||
public function toMail(object $notifiable): MailMessage
|
public function toMail(object $notifiable): MailMessage
|
||||||
{
|
{
|
||||||
$this->application->loadMissing('applicant');
|
$this->application->loadMissing('applicant');
|
||||||
|
$applicant = $this->application->applicant;
|
||||||
|
|
||||||
return (new MailMessage)
|
return $this->mailMessage(
|
||||||
->subject('Permohonan Keahlian Diluluskan')
|
subject: 'Keputusan Permohonan Keahlian KOPKB - LULUS',
|
||||||
->markdown('membershipapplication::emails.passed', [
|
view: 'membershipapplication::emails.passed',
|
||||||
'name' => $this->application->applicant->name,
|
data: [
|
||||||
'applicationNumber' => $this->application->application_number,
|
'application' => $this->application,
|
||||||
'logoPath' => public_path('images/logo-kopkb.svg'),
|
'applicant' => $applicant,
|
||||||
]);
|
],
|
||||||
|
)->attach(
|
||||||
|
Storage::disk(Document::STORAGE_DISK)->path($this->resultLetter->path),
|
||||||
|
[
|
||||||
|
'as' => $this->resultLetter->name,
|
||||||
|
'mime' => $this->resultLetter->mime_type,
|
||||||
|
]
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Modules\MembershipApplication\Emails;
|
namespace Modules\MembershipApplication\Emails;
|
||||||
|
|
||||||
|
use App\Notifications\Concerns\BuildsMailMessage;
|
||||||
use Illuminate\Bus\Queueable;
|
use Illuminate\Bus\Queueable;
|
||||||
use Illuminate\Notifications\Messages\MailMessage;
|
use Illuminate\Notifications\Messages\MailMessage;
|
||||||
use Illuminate\Notifications\Notification;
|
use Illuminate\Notifications\Notification;
|
||||||
@@ -9,6 +10,7 @@ use Modules\MembershipApplication\Entities\MembershipApplication;
|
|||||||
|
|
||||||
class MembershipApplicationSubmittedNotification extends Notification
|
class MembershipApplicationSubmittedNotification extends Notification
|
||||||
{
|
{
|
||||||
|
use BuildsMailMessage;
|
||||||
use Queueable;
|
use Queueable;
|
||||||
|
|
||||||
public function __construct(
|
public function __construct(
|
||||||
@@ -24,9 +26,10 @@ class MembershipApplicationSubmittedNotification extends Notification
|
|||||||
{
|
{
|
||||||
$this->application->loadMissing(['applicant', 'heirs', 'documents', 'references.member']);
|
$this->application->loadMissing(['applicant', 'heirs', 'documents', 'references.member']);
|
||||||
|
|
||||||
return (new MailMessage)
|
return $this->mailMessage(
|
||||||
->subject('Pengesahan Permohonan Keahlian')
|
subject: 'Pengesahan Permohonan Keahlian',
|
||||||
->markdown('membershipapplication::emails.submitted', [
|
view: 'membershipapplication::emails.submitted',
|
||||||
|
data: [
|
||||||
'application' => $this->application,
|
'application' => $this->application,
|
||||||
'applicant' => $this->application->applicant,
|
'applicant' => $this->application->applicant,
|
||||||
'heirs' => $this->application->heirs,
|
'heirs' => $this->application->heirs,
|
||||||
@@ -37,7 +40,7 @@ class MembershipApplicationSubmittedNotification extends Notification
|
|||||||
'salary_slip' => 'Slip Gaji',
|
'salary_slip' => 'Slip Gaji',
|
||||||
'employer_letter' => 'Surat Pengesahan Majikan',
|
'employer_letter' => 'Surat Pengesahan Majikan',
|
||||||
],
|
],
|
||||||
'logoPath' => public_path('images/logo-kopkb.svg'),
|
],
|
||||||
]);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,6 +18,8 @@ class MembershipApplication extends Model
|
|||||||
|
|
||||||
public const RESULT_LETTER_DOCUMENT_TYPE = 'result_letter';
|
public const RESULT_LETTER_DOCUMENT_TYPE = 'result_letter';
|
||||||
|
|
||||||
|
public const ADMIN_ATTACHMENT_DOCUMENT_TYPE = 'admin_attachment';
|
||||||
|
|
||||||
public const MINIMUM_SHARE_CAPITAL = 500;
|
public const MINIMUM_SHARE_CAPITAL = 500;
|
||||||
|
|
||||||
public const SHARE_INSTALLMENT_MONTHS = 6;
|
public const SHARE_INSTALLMENT_MONTHS = 6;
|
||||||
|
|||||||
@@ -17,6 +17,7 @@ use Modules\MembershipApplication\Enums\ManagementDecision;
|
|||||||
use Modules\MembershipApplication\Http\Requests\AssignReferencesRequest;
|
use Modules\MembershipApplication\Http\Requests\AssignReferencesRequest;
|
||||||
use Modules\MembershipApplication\Http\Requests\BatchCompleteRequest;
|
use Modules\MembershipApplication\Http\Requests\BatchCompleteRequest;
|
||||||
use Modules\MembershipApplication\Http\Requests\BoardReviewRequest;
|
use Modules\MembershipApplication\Http\Requests\BoardReviewRequest;
|
||||||
|
use Modules\MembershipApplication\Http\Requests\CompleteMembershipApplicationRequest;
|
||||||
use Modules\MembershipApplication\Http\Requests\GenerateResultLetterRequest;
|
use Modules\MembershipApplication\Http\Requests\GenerateResultLetterRequest;
|
||||||
use Modules\MembershipApplication\Http\Requests\ManagementReviewRequest;
|
use Modules\MembershipApplication\Http\Requests\ManagementReviewRequest;
|
||||||
use Modules\MembershipApplication\Http\Requests\UpdateMembershipApplicationRequest;
|
use Modules\MembershipApplication\Http\Requests\UpdateMembershipApplicationRequest;
|
||||||
@@ -180,18 +181,19 @@ class MembershipApplicationController extends Controller
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
public function complete(Request $request, MembershipApplication $membershipApplication): JsonResponse
|
public function complete(CompleteMembershipApplicationRequest $request, MembershipApplication $membershipApplication): JsonResponse
|
||||||
{
|
{
|
||||||
$this->authorize('complete', $membershipApplication);
|
$this->authorize('complete', $membershipApplication);
|
||||||
|
|
||||||
$application = $this->membershipApplicationService->complete(
|
$application = $this->membershipApplicationService->complete(
|
||||||
$membershipApplication,
|
$membershipApplication,
|
||||||
$request->user()
|
$request->user(),
|
||||||
|
$request->validated('board_meeting_reference'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$message = $application->board_result === BoardResult::Pass->value
|
$message = $application->board_result === BoardResult::Pass->value
|
||||||
? 'Permohonan selesai. Akaun ahli telah dicipta dan e-mel dihantar.'
|
? 'Permohonan selesai. Surat keputusan dijana, akaun ahli dicipta dan e-mel dihantar.'
|
||||||
: 'Permohonan selesai. E-mel keputusan dihantar.';
|
: 'Permohonan selesai. Surat keputusan dijana dan e-mel dihantar.';
|
||||||
|
|
||||||
return response()->json([
|
return response()->json([
|
||||||
'success' => true,
|
'success' => true,
|
||||||
@@ -206,7 +208,8 @@ class MembershipApplicationController extends Controller
|
|||||||
|
|
||||||
$result = $this->membershipApplicationService->completeBatch(
|
$result = $this->membershipApplicationService->completeBatch(
|
||||||
$request->validated('application_ids'),
|
$request->validated('application_ids'),
|
||||||
$request->user()
|
$request->user(),
|
||||||
|
$request->validated('board_meeting_reference'),
|
||||||
);
|
);
|
||||||
|
|
||||||
$succeededCount = count($result['succeeded']);
|
$succeededCount = count($result['succeeded']);
|
||||||
@@ -245,6 +248,22 @@ class MembershipApplicationController extends Controller
|
|||||||
return $this->documentService->downloadDocument($document->id);
|
return $this->documentService->downloadDocument($document->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deleteDocument(MembershipApplication $membershipApplication, string $documentId): JsonResponse
|
||||||
|
{
|
||||||
|
$this->authorize('update', $membershipApplication);
|
||||||
|
|
||||||
|
$application = $this->membershipApplicationService->deleteDocument(
|
||||||
|
$membershipApplication,
|
||||||
|
$documentId,
|
||||||
|
);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Lampiran pentadbir berjaya dipadam.',
|
||||||
|
'data' => new MembershipApplicationResource($application),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function generateResultLetter(GenerateResultLetterRequest $request, MembershipApplication $membershipApplication,): JsonResponse {
|
public function generateResultLetter(GenerateResultLetterRequest $request, MembershipApplication $membershipApplication,): JsonResponse {
|
||||||
$this->authorize('generateResultLetter', $membershipApplication);
|
$this->authorize('generateResultLetter', $membershipApplication);
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ class BatchCompleteRequest extends FormRequest
|
|||||||
return [
|
return [
|
||||||
'application_ids' => 'required|array|min:1|max:100',
|
'application_ids' => 'required|array|min:1|max:100',
|
||||||
'application_ids.*' => 'required|uuid|distinct|exists:membership_applications,id',
|
'application_ids.*' => 'required|uuid|distinct|exists:membership_applications,id',
|
||||||
|
'board_meeting_reference' => 'required|string|max:255',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -33,6 +34,8 @@ class BatchCompleteRequest extends FormRequest
|
|||||||
'application_ids.max' => 'Maksimum 100 permohonan setiap batch.',
|
'application_ids.max' => 'Maksimum 100 permohonan setiap batch.',
|
||||||
'application_ids.*.exists' => 'Salah satu permohonan tidak dijumpai.',
|
'application_ids.*.exists' => 'Salah satu permohonan tidak dijumpai.',
|
||||||
'application_ids.*.distinct' => 'Permohonan duplikat tidak dibenarkan.',
|
'application_ids.*.distinct' => 'Permohonan duplikat tidak dibenarkan.',
|
||||||
|
'board_meeting_reference.required' => 'Rujukan mesyuarat lembaga diperlukan.',
|
||||||
|
'board_meeting_reference.max' => 'Rujukan mesyuarat lembaga tidak boleh melebihi 255 aksara.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\MembershipApplication\Http\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class CompleteMembershipApplicationRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, \Illuminate\Contracts\Validation\ValidationRule|array<mixed>|string>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'board_meeting_reference' => 'required|string|max:255',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function messages(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'board_meeting_reference.required' => 'Rujukan mesyuarat lembaga diperlukan.',
|
||||||
|
'board_meeting_reference.max' => 'Rujukan mesyuarat lembaga tidak boleh melebihi 255 aksara.',
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -48,7 +48,7 @@ class StoreMembershipApplicationRequest extends FormRequest
|
|||||||
'documents.ic_copy' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
'documents.ic_copy' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||||
'documents.photo' => 'nullable|file|mimes:jpg,jpeg,png|max:10240',
|
'documents.photo' => 'nullable|file|mimes:jpg,jpeg,png|max:10240',
|
||||||
'documents.salary_slip' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
'documents.salary_slip' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||||
'documents.employer_letter' => 'nullable|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
'documents.employer_letter' => 'required|file|mimes:pdf,jpg,jpeg,png|max:10240',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -89,6 +89,8 @@ class StoreMembershipApplicationRequest extends FormRequest
|
|||||||
|
|
||||||
'documents.ic_copy.required' => 'Salinan kad pengenalan diperlukan.',
|
'documents.ic_copy.required' => 'Salinan kad pengenalan diperlukan.',
|
||||||
'documents.ic_copy.mimes' => 'Salinan kad pengenalan mestilah PDF, JPG, JPEG atau PNG.',
|
'documents.ic_copy.mimes' => 'Salinan kad pengenalan mestilah PDF, JPG, JPEG atau PNG.',
|
||||||
|
'documents.employer_letter.required' => 'Surat pengesahan majikan diperlukan.',
|
||||||
|
'documents.employer_letter.mimes' => 'Surat pengesahan majikan mestilah PDF, JPG, JPEG atau PNG.',
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ namespace Modules\MembershipApplication\Http\Requests;
|
|||||||
|
|
||||||
use Illuminate\Foundation\Http\FormRequest;
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
use Illuminate\Validation\Rule;
|
use Illuminate\Validation\Rule;
|
||||||
|
use Modules\MembershipApplication\Entities\MembershipApplication;
|
||||||
|
|
||||||
class UploadMembershipApplicationDocumentRequest extends FormRequest
|
class UploadMembershipApplicationDocumentRequest extends FormRequest
|
||||||
{
|
{
|
||||||
@@ -18,7 +19,13 @@ class UploadMembershipApplicationDocumentRequest extends FormRequest
|
|||||||
public function rules(): array
|
public function rules(): array
|
||||||
{
|
{
|
||||||
return [
|
return [
|
||||||
'type' => ['required', Rule::in(['ic_copy', 'photo', 'salary_slip', 'employer_letter'])],
|
'type' => ['required', Rule::in([
|
||||||
|
'ic_copy',
|
||||||
|
'photo',
|
||||||
|
'salary_slip',
|
||||||
|
'employer_letter',
|
||||||
|
MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE,
|
||||||
|
])],
|
||||||
'file' => [
|
'file' => [
|
||||||
'required',
|
'required',
|
||||||
'file',
|
'file',
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ class MembershipApplicationRepository implements MembershipApplicationRepository
|
|||||||
return MembershipApplication::with([
|
return MembershipApplication::with([
|
||||||
'applicant',
|
'applicant',
|
||||||
'heirs',
|
'heirs',
|
||||||
|
'user:id,member_number',
|
||||||
'references.member:id,name,ic_number',
|
'references.member:id,name,ic_number',
|
||||||
'documents',
|
'documents',
|
||||||
'reviews.reviewer:id,name',
|
'reviews.reviewer:id,name',
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
@component('mail::message')
|
@component('mail::message')
|
||||||
@if (! empty($logoPath) && file_exists($logoPath))
|
@include('emails.partials.header')
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
|
||||||
<img src="{{ $message->embed($logoPath) }}" alt="{{ config('app.name') }}" style="max-height: 80px; width: auto;">
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
# Keputusan Permohonan Keahlian
|
# Keputusan Permohonan Keahlian
|
||||||
|
|
||||||
Assalamualaikum **{{ $name }}**,
|
Assalamualaikum **{{ $applicant->name }}**,
|
||||||
|
|
||||||
Permohonan keahlian anda (**{{ $applicationNumber }}**) **tidak diluluskan** pada peringkat lembaga.
|
Permohonan keahlian anda (**{{ $application->application_number }}**) **tidak diluluskan** pada peringkat lembaga.
|
||||||
|
|
||||||
|
Surat keputusan rasmi dilampirkan dalam e-mel ini.
|
||||||
|
|
||||||
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
||||||
|
|
||||||
Terima kasih,<br>
|
@include('emails.partials.footer')
|
||||||
{{ config('app.name') }}
|
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|||||||
@@ -1,18 +1,15 @@
|
|||||||
@component('mail::message')
|
@component('mail::message')
|
||||||
@if (! empty($logoPath) && file_exists($logoPath))
|
@include('emails.partials.header')
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
|
||||||
<img src="{{ $message->embed($logoPath) }}" alt="{{ config('app.name') }}" style="max-height: 80px; width: auto;">
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
# Keputusan Permohonan Keahlian
|
# Keputusan Permohonan Keahlian
|
||||||
|
|
||||||
Assalamualaikum **{{ $name }}**,
|
Assalamualaikum **{{ $applicant->name }}**,
|
||||||
|
|
||||||
Permohonan keahlian anda (**{{ $applicationNumber }}**) **diluluskan** pada peringkat lembaga.
|
Permohonan keahlian anda (**{{ $application->application_number }}**) **diluluskan** pada peringkat lembaga.
|
||||||
|
|
||||||
|
Surat keputusan rasmi dilampirkan dalam e-mel ini.
|
||||||
|
|
||||||
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
Terima kasih atas minat anda. Untuk sebarang pertanyaan, sila hubungi pejabat koperasi.
|
||||||
|
|
||||||
Terima kasih,<br>
|
@include('emails.partials.footer')
|
||||||
{{ config('app.name') }}
|
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
@component('mail::message')
|
@component('mail::message')
|
||||||
@if (! empty($logoPath) && file_exists($logoPath))
|
@include('emails.partials.header')
|
||||||
<div style="text-align: center; margin-bottom: 24px;">
|
|
||||||
<img src="{{ $message->embed($logoPath) }}" alt="{{ config('app.name') }}" style="max-height: 80px; width: auto;">
|
|
||||||
</div>
|
|
||||||
@endif
|
|
||||||
|
|
||||||
# Pengesahan Permohonan Keahlian
|
# Pengesahan Permohonan Keahlian
|
||||||
|
|
||||||
@@ -74,6 +70,5 @@ Tiada dokumen.
|
|||||||
|
|
||||||
Anda akan menerima emel apabila keputusan permohonan tersedia.
|
Anda akan menerima emel apabila keputusan permohonan tersedia.
|
||||||
|
|
||||||
Terima kasih,<br>
|
@include('emails.partials.footer')
|
||||||
{{ config('app.name') }}
|
|
||||||
@endcomponent
|
@endcomponent
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="line-height: 1;">
|
<p style="line-height: 1;">
|
||||||
2. {{ $openingTone }} dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
2. {{ $openingTone }} dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
||||||
Permodalan Kelantan Berhad (KoPKB) telah <strong>{{ $boardResultLabel }}</strong> dalam {{ $boardMeetingReference }}.
|
Permodalan Kelantan Berhad (KoPKB) telah <strong>{{ $boardResultLabel }}</strong> dalam {{ $boardMeetingReference }}@if ($isPassed && $memberNumber). Nombor keanggotaan tuan adalah <strong>{{ $memberNumber }}</strong>@endif.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
@if ($isPassed)
|
@if ($isPassed)
|
||||||
@@ -66,8 +66,12 @@
|
|||||||
@section('letter-signature')
|
@section('letter-signature')
|
||||||
<div class="signature-header">Dengan hormatnya,</div>
|
<div class="signature-header">Dengan hormatnya,</div>
|
||||||
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
||||||
<div class="signature-space"></div>
|
<div class="signature-space">
|
||||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDDIN</div>
|
@if (! empty($signatureUrl))
|
||||||
|
<img src="{{ $signatureUrl }}" alt="" class="signature-space-img">
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDIN</div>
|
||||||
<div class="signatory-title">Pengurus Besar</div>
|
<div class="signatory-title">Pengurus Besar</div>
|
||||||
<div class="signatory-title">b/p Setiausaha</div>
|
<div class="signatory-title">b/p Setiausaha</div>
|
||||||
@endsection
|
@endsection
|
||||||
|
|||||||
@@ -38,6 +38,7 @@ Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(funct
|
|||||||
|
|
||||||
// Membership application upload document routes
|
// Membership application upload document routes
|
||||||
Route::post('membership-applications/{membershipApplication}/documents', [MembershipApplicationController::class, 'uploadDocument'])->name('membership-application.upload-document');
|
Route::post('membership-applications/{membershipApplication}/documents', [MembershipApplicationController::class, 'uploadDocument'])->name('membership-application.upload-document');
|
||||||
|
Route::delete('membership-applications/{membershipApplication}/documents/{documentId}', [MembershipApplicationController::class, 'deleteDocument'])->name('membership-application.delete-document');
|
||||||
// download document routes
|
// download document routes
|
||||||
Route::get('membership-applications/{membershipApplication}/documents/{documentId}/download', [MembershipApplicationController::class, 'downloadDocument'])->name('membership-application.download-document');
|
Route::get('membership-applications/{membershipApplication}/documents/{documentId}/download', [MembershipApplicationController::class, 'downloadDocument'])->name('membership-application.download-document');
|
||||||
|
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
namespace Modules\MembershipApplication\Services;
|
namespace Modules\MembershipApplication\Services;
|
||||||
|
|
||||||
|
use App\Models\Document;
|
||||||
use App\Services\DocumentService;
|
use App\Services\DocumentService;
|
||||||
use App\Services\LetterService;
|
use App\Services\LetterService;
|
||||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||||
@@ -137,15 +138,38 @@ class MembershipApplicationService
|
|||||||
|
|
||||||
$this->documentService->validateFile($file);
|
$this->documentService->validateFile($file);
|
||||||
|
|
||||||
$application->documentsOfType($type)->get()->each(
|
if ($type !== MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE) {
|
||||||
fn ($document) => $this->documentService->deleteDocument($document->id)
|
$application->documentsOfType($type)->get()->each(
|
||||||
);
|
fn ($document) => $this->documentService->deleteDocument($document->id)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
$this->documentService->uploadDocument($application, $file, $type);
|
$this->documentService->uploadDocument($application, $file, $type);
|
||||||
|
|
||||||
return $this->repository->findByIdWithRelations($application->id);
|
return $this->repository->findByIdWithRelations($application->id);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function deleteDocument(MembershipApplication $application, string $documentId): MembershipApplication
|
||||||
|
{
|
||||||
|
if ($application->status === ApplicationStatus::Completed) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'status' => ['Permohonan yang telah selesai tidak boleh dikemaskini.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$document = $application->documents()->findOrFail($documentId);
|
||||||
|
|
||||||
|
if ($document->type !== MembershipApplication::ADMIN_ATTACHMENT_DOCUMENT_TYPE) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'document' => ['Hanya lampiran pentadbir boleh dipadam.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$this->documentService->deleteDocument($document->id);
|
||||||
|
|
||||||
|
return $this->repository->findByIdWithRelations($application->id);
|
||||||
|
}
|
||||||
|
|
||||||
public function managementReview(MembershipApplication $application, ManagementDecision $decision, ?string $remarks, User $reviewer): MembershipApplication {
|
public function managementReview(MembershipApplication $application, ManagementDecision $decision, ?string $remarks, User $reviewer): MembershipApplication {
|
||||||
if ($application->status !== ApplicationStatus::Submitted) {
|
if ($application->status !== ApplicationStatus::Submitted) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
@@ -230,8 +254,11 @@ class MembershipApplicationService
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public function complete(MembershipApplication $application, User $processedBy): MembershipApplication
|
public function complete(
|
||||||
{
|
MembershipApplication $application,
|
||||||
|
User $processedBy,
|
||||||
|
string $boardMeetingReference,
|
||||||
|
): MembershipApplication {
|
||||||
if ($application->status !== ApplicationStatus::PendingNotification) {
|
if ($application->status !== ApplicationStatus::PendingNotification) {
|
||||||
throw ValidationException::withMessages([
|
throw ValidationException::withMessages([
|
||||||
'status' => ['Permohonan ini tidak boleh diselesaikan pada masa ini.'],
|
'status' => ['Permohonan ini tidak boleh diselesaikan pada masa ini.'],
|
||||||
@@ -244,13 +271,16 @@ class MembershipApplicationService
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($application) {
|
return DB::transaction(function () use ($application, $boardMeetingReference) {
|
||||||
if ($application->board_result === BoardResult::Pass->value && ! $application->user_id) {
|
if ($application->board_result === BoardResult::Pass->value && ! $application->user_id) {
|
||||||
$user = $this->createMemberFromApplication($application);
|
$user = $this->createMemberFromApplication($application);
|
||||||
$application->update(['user_id' => $user->id]);
|
$application->update(['user_id' => $user->id]);
|
||||||
|
$application->refresh();
|
||||||
}
|
}
|
||||||
|
|
||||||
$this->sendResultNotification($application);
|
$resultLetter = $this->storeResultLetter($application, $boardMeetingReference);
|
||||||
|
|
||||||
|
$this->sendResultNotification($application, $resultLetter);
|
||||||
|
|
||||||
$application->update([
|
$application->update([
|
||||||
'status' => ApplicationStatus::Completed,
|
'status' => ApplicationStatus::Completed,
|
||||||
@@ -268,7 +298,7 @@ class MembershipApplicationService
|
|||||||
* failed: list<array{id: string, application_number: string|null, message: string}>
|
* failed: list<array{id: string, application_number: string|null, message: string}>
|
||||||
* }
|
* }
|
||||||
*/
|
*/
|
||||||
public function completeBatch(array $applicationIds, User $processedBy): array
|
public function completeBatch(array $applicationIds, User $processedBy, string $boardMeetingReference): array
|
||||||
{
|
{
|
||||||
$succeeded = [];
|
$succeeded = [];
|
||||||
$failed = [];
|
$failed = [];
|
||||||
@@ -287,7 +317,7 @@ class MembershipApplicationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
$succeeded[] = $this->complete($application, $processedBy);
|
$succeeded[] = $this->complete($application, $processedBy, $boardMeetingReference);
|
||||||
} catch (ValidationException $e) {
|
} catch (ValidationException $e) {
|
||||||
$failed[] = [
|
$failed[] = [
|
||||||
'id' => $id,
|
'id' => $id,
|
||||||
@@ -317,40 +347,47 @@ class MembershipApplicationService
|
|||||||
}
|
}
|
||||||
|
|
||||||
return DB::transaction(function () use ($application, $boardMeetingReference) {
|
return DB::transaction(function () use ($application, $boardMeetingReference) {
|
||||||
$lockedApplication = MembershipApplication::query()
|
$this->storeResultLetter($application, $boardMeetingReference);
|
||||||
->whereKey($application->id)
|
|
||||||
->lockForUpdate()
|
|
||||||
->firstOrFail();
|
|
||||||
|
|
||||||
if ($lockedApplication->hasDocumentsOfType(MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE)) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'result_letter' => ['Surat keputusan telah dijana dan tidak boleh dijana semula.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$application = $this->repository->findByIdWithRelations($lockedApplication->id);
|
|
||||||
|
|
||||||
if (! $application || ! $application->applicant) {
|
|
||||||
throw ValidationException::withMessages([
|
|
||||||
'application' => ['Maklumat pemohon tidak lengkap.'],
|
|
||||||
]);
|
|
||||||
}
|
|
||||||
|
|
||||||
$viewData = $this->buildResultLetterViewData($application, $boardMeetingReference);
|
|
||||||
$pdf = $this->letterService->renderPdf('membershipapplication::pdf.approved-letter', $viewData);
|
|
||||||
$fileName = 'surat-keputusan-'.$application->application_number.'.pdf';
|
|
||||||
|
|
||||||
$this->documentService->storeGeneratedDocument(
|
|
||||||
$application,
|
|
||||||
$pdf,
|
|
||||||
$fileName,
|
|
||||||
MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE,
|
|
||||||
);
|
|
||||||
|
|
||||||
return $this->repository->findByIdWithRelations($application->id);
|
return $this->repository->findByIdWithRelations($application->id);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function storeResultLetter(
|
||||||
|
MembershipApplication $application,
|
||||||
|
string $boardMeetingReference,
|
||||||
|
): Document {
|
||||||
|
$lockedApplication = MembershipApplication::query()
|
||||||
|
->whereKey($application->id)
|
||||||
|
->lockForUpdate()
|
||||||
|
->firstOrFail();
|
||||||
|
|
||||||
|
if ($lockedApplication->hasDocumentsOfType(MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE)) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'result_letter' => ['Surat keputusan telah dijana dan tidak boleh dijana semula.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$application = $this->repository->findByIdWithRelations($lockedApplication->id);
|
||||||
|
|
||||||
|
if (! $application || ! $application->applicant) {
|
||||||
|
throw ValidationException::withMessages([
|
||||||
|
'application' => ['Maklumat pemohon tidak lengkap.'],
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$viewData = $this->buildResultLetterViewData($application, $boardMeetingReference);
|
||||||
|
$pdf = $this->letterService->renderPdf('membershipapplication::pdf.approved-letter', $viewData);
|
||||||
|
$fileName = 'surat-keputusan-'.$application->application_number.'.pdf';
|
||||||
|
|
||||||
|
return $this->documentService->storeGeneratedDocument(
|
||||||
|
$application,
|
||||||
|
$pdf,
|
||||||
|
$fileName,
|
||||||
|
MembershipApplication::RESULT_LETTER_DOCUMENT_TYPE,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @return array<string, mixed>
|
* @return array<string, mixed>
|
||||||
*/
|
*/
|
||||||
@@ -378,8 +415,9 @@ class MembershipApplicationService
|
|||||||
): array {
|
): array {
|
||||||
$applicant = $application->applicant;
|
$applicant = $application->applicant;
|
||||||
$isPassed = $application->board_result === BoardResult::Pass->value;
|
$isPassed = $application->board_result === BoardResult::Pass->value;
|
||||||
|
$stockMonthlyContribution = (float) $applicant->stock_monthly_contribution;
|
||||||
$monthlyShareInstallment = number_format(
|
$monthlyShareInstallment = number_format(
|
||||||
MembershipApplication::MINIMUM_SHARE_CAPITAL / MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
$stockMonthlyContribution < 84 ? 84 : $stockMonthlyContribution,
|
||||||
2,
|
2,
|
||||||
'.',
|
'.',
|
||||||
'',
|
'',
|
||||||
@@ -396,10 +434,11 @@ class MembershipApplicationService
|
|||||||
? 'KELULUSAN KEANGGOTAAN KOPERASI PERMODALAN KELANTAN BERHAD'
|
? 'KELULUSAN KEANGGOTAAN KOPERASI PERMODALAN KELANTAN BERHAD'
|
||||||
: 'KEPUTUSAN PERMOHONAN KEANGGOTAAN KOPERASI PERMODALAN KELANTAN BERHAD',
|
: 'KEPUTUSAN PERMOHONAN KEANGGOTAAN KOPERASI PERMODALAN KELANTAN BERHAD',
|
||||||
'monthlyShareInstallment' => $monthlyShareInstallment,
|
'monthlyShareInstallment' => $monthlyShareInstallment,
|
||||||
'stockMonthlyContribution' => number_format((float) $applicant->stock_monthly_contribution, 2, '.', ''),
|
'stockMonthlyContribution' => number_format($stockMonthlyContribution, 2, '.', ''),
|
||||||
'feeMonthlyContribution' => number_format((float) $applicant->fee_monthly_contribution, 2, '.', ''),
|
'feeMonthlyContribution' => number_format((float) $applicant->fee_monthly_contribution, 2, '.', ''),
|
||||||
'shareInstallmentMonths' => MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
'shareInstallmentMonths' => MembershipApplication::SHARE_INSTALLMENT_MONTHS,
|
||||||
'minimumShareCapital' => number_format(MembershipApplication::MINIMUM_SHARE_CAPITAL, 2, '.', ''),
|
'minimumShareCapital' => number_format(MembershipApplication::MINIMUM_SHARE_CAPITAL, 2, '.', ''),
|
||||||
|
'memberNumber' => $isPassed ? $application->user?->member_number : null,
|
||||||
];
|
];
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -488,20 +527,30 @@ class MembershipApplicationService
|
|||||||
->notify(new MembershipApplicationSubmittedNotification($application));
|
->notify(new MembershipApplicationSubmittedNotification($application));
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function sendResultNotification(MembershipApplication $application): void
|
protected function sendResultNotification(MembershipApplication $application, Document $resultLetter): void
|
||||||
{
|
{
|
||||||
$application->loadMissing('applicant');
|
$application->loadMissing('applicant');
|
||||||
|
|
||||||
$notification = $application->board_result === BoardResult::Pass->value
|
$notification = $application->board_result === BoardResult::Pass->value
|
||||||
? new MembershipApplicationPassedNotification($application)
|
? new MembershipApplicationPassedNotification($application, $resultLetter)
|
||||||
: new MembershipApplicationFailedNotification($application);
|
: new MembershipApplicationFailedNotification($application, $resultLetter);
|
||||||
|
|
||||||
Notification::route('mail', $application->applicant->email)->notify($notification);
|
Notification::route('mail', $application->applicant->email)->notify($notification);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function generateMemberNumber(): int
|
protected function generateMemberNumber(): int
|
||||||
{
|
{
|
||||||
return ((int) User::max('member_number')) + 1;
|
$latestUser = User::query()
|
||||||
|
->whereNotNull('member_number')
|
||||||
|
->orderByDesc('member_number')
|
||||||
|
->lockForUpdate()
|
||||||
|
->first();
|
||||||
|
|
||||||
|
if ($latestUser === null && DB::getDriverName() === 'pgsql') {
|
||||||
|
DB::selectOne('SELECT pg_advisory_xact_lock(?) AS locked', [742891035]);
|
||||||
|
}
|
||||||
|
|
||||||
|
return ((int) ($latestUser?->member_number ?? 0)) + 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -2,4 +2,7 @@
|
|||||||
|
|
||||||
return [
|
return [
|
||||||
'name' => 'User',
|
'name' => 'User',
|
||||||
|
'public_profile_token_ttl_days' => (int) env('PUBLIC_PROFILE_TOKEN_TTL_DAYS', 90),
|
||||||
|
'frontend_url' => rtrim(env('FRONTEND_URL', env('APP_URL', 'http://localhost')), '/'),
|
||||||
|
'digital_card_logo_path' => env('DIGITAL_CARD_LOGO_PATH', public_path('images/logo/logo.svg')),
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -0,0 +1,34 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use Modules\Auth\Entities\User;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->string('public_profile_token', 64)->nullable()->unique()->after('member_type');
|
||||||
|
});
|
||||||
|
|
||||||
|
User::withTrashed()
|
||||||
|
->whereNull('public_profile_token')
|
||||||
|
->cursor()
|
||||||
|
->each(function (User $user) {
|
||||||
|
$user->forceFill([
|
||||||
|
'public_profile_token' => Str::random(48),
|
||||||
|
])->saveQuietly();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropUnique(['public_profile_token']);
|
||||||
|
$table->dropColumn('public_profile_token');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
use Modules\Auth\Entities\User;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->timestamp('public_profile_token_expires_at')->nullable()->after('public_profile_token');
|
||||||
|
});
|
||||||
|
|
||||||
|
$expiryDays = (int) config('user.public_profile_token_ttl_days', 90);
|
||||||
|
|
||||||
|
User::withTrashed()
|
||||||
|
->whereNotNull('public_profile_token')
|
||||||
|
->whereNull('public_profile_token_expires_at')
|
||||||
|
->cursor()
|
||||||
|
->each(function (User $user) use ($expiryDays) {
|
||||||
|
$user->forceFill([
|
||||||
|
'public_profile_token_expires_at' => now()->addDays($expiryDays),
|
||||||
|
])->saveQuietly();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('public_profile_token_expires_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,22 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
use Illuminate\Database\Migrations\Migration;
|
||||||
|
use Illuminate\Database\Schema\Blueprint;
|
||||||
|
use Illuminate\Support\Facades\Schema;
|
||||||
|
|
||||||
|
return new class extends Migration
|
||||||
|
{
|
||||||
|
public function up(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->timestamp('onboarding_completed_at')->nullable()->after('birth_place');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public function down(): void
|
||||||
|
{
|
||||||
|
Schema::table('users', function (Blueprint $table) {
|
||||||
|
$table->dropColumn('onboarding_completed_at');
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Modules\User\Services\MemberDigitalCardService;
|
||||||
|
use Symfony\Component\HttpFoundation\Response;
|
||||||
|
|
||||||
|
class MemberDigitalCardController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected MemberDigitalCardService $memberDigitalCardService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function download(Request $request): Response|JsonResponse
|
||||||
|
{
|
||||||
|
$validated = $request->validate([
|
||||||
|
'side' => 'sometimes|in:depan,belakang',
|
||||||
|
]);
|
||||||
|
|
||||||
|
$user = $request->user();
|
||||||
|
$side = $validated['side'] ?? 'depan';
|
||||||
|
|
||||||
|
if ($user->status !== 'active') {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Kad digital hanya tersedia untuk anggota aktif.',
|
||||||
|
'code' => 'digital_card_inactive',
|
||||||
|
'data' => null,
|
||||||
|
], 403);
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
$png = $this->memberDigitalCardService->renderPng($user, $side);
|
||||||
|
$memberNumber = $user->member_number ?: 'anggota';
|
||||||
|
$filename = sprintf('kad-digital-%s-%s.png', $memberNumber, $side);
|
||||||
|
|
||||||
|
return response($png, Response::HTTP_OK, [
|
||||||
|
'Content-Type' => 'image/png',
|
||||||
|
'Content-Disposition' => 'attachment; filename="'.$filename.'"',
|
||||||
|
'Cache-Control' => 'no-store, private',
|
||||||
|
]);
|
||||||
|
} catch (\Throwable $exception) {
|
||||||
|
Log::error('Failed to render member digital card.', [
|
||||||
|
'user_id' => $user->id,
|
||||||
|
'side' => $side,
|
||||||
|
'message' => $exception->getMessage(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Gagal menjana kad digital. Sila cuba lagi.',
|
||||||
|
'code' => 'digital_card_render_failed',
|
||||||
|
'data' => null,
|
||||||
|
], 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,37 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Http\Controllers;
|
||||||
|
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
use Modules\User\Services\PublicMemberProfileService;
|
||||||
|
use Modules\User\Transformers\PublicMemberProfileResource;
|
||||||
|
|
||||||
|
class PublicMemberProfileController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected PublicMemberProfileService $publicMemberProfileService
|
||||||
|
) {}
|
||||||
|
|
||||||
|
public function show(string $token): JsonResponse
|
||||||
|
{
|
||||||
|
$member = $this->publicMemberProfileService->findMemberByToken($token);
|
||||||
|
|
||||||
|
if (! $member) {
|
||||||
|
return response()->json([
|
||||||
|
'success' => false,
|
||||||
|
'message' => 'Anggota tidak dijumpai atau tidak sah.',
|
||||||
|
'code' => 'public_profile_not_found',
|
||||||
|
'data' => null,
|
||||||
|
], 404);
|
||||||
|
}
|
||||||
|
|
||||||
|
$member = $this->publicMemberProfileService->loadVerifiableRelations($member);
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'message' => 'Anggota disahkan.',
|
||||||
|
'data' => new PublicMemberProfileResource($member),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -127,6 +127,17 @@ class UserController extends BaseCrudController
|
|||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public function completeOnboarding(Request $request): JsonResponse
|
||||||
|
{
|
||||||
|
$user = $this->userService->completeOnboarding($request->user());
|
||||||
|
|
||||||
|
return response()->json([
|
||||||
|
'success' => true,
|
||||||
|
'data' => new UserResource($user),
|
||||||
|
'message' => 'Onboarding completed.',
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
|
||||||
public function updatePassword(Request $request): JsonResponse
|
public function updatePassword(Request $request): JsonResponse
|
||||||
{
|
{
|
||||||
$user = $request->user();
|
$user = $request->user();
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ namespace Modules\User\Providers;
|
|||||||
use Illuminate\Support\Facades\Blade;
|
use Illuminate\Support\Facades\Blade;
|
||||||
use Illuminate\Support\Facades\Gate;
|
use Illuminate\Support\Facades\Gate;
|
||||||
use Illuminate\Support\ServiceProvider;
|
use Illuminate\Support\ServiceProvider;
|
||||||
|
use Illuminate\Support\Facades\RateLimiter;
|
||||||
|
use Illuminate\Cache\RateLimiting\Limit;
|
||||||
use Modules\Auth\Entities\User;
|
use Modules\Auth\Entities\User;
|
||||||
use Modules\User\Policies\UserPolicy;
|
use Modules\User\Policies\UserPolicy;
|
||||||
use Modules\User\Entities\Address;
|
use Modules\User\Entities\Address;
|
||||||
@@ -34,6 +36,7 @@ class UserServiceProvider extends ServiceProvider
|
|||||||
*/
|
*/
|
||||||
public function boot(): void
|
public function boot(): void
|
||||||
{
|
{
|
||||||
|
$this->registerRateLimiters();
|
||||||
$this->registerCommands();
|
$this->registerCommands();
|
||||||
$this->registerCommandSchedules();
|
$this->registerCommandSchedules();
|
||||||
$this->registerTranslations();
|
$this->registerTranslations();
|
||||||
@@ -96,6 +99,13 @@ class UserServiceProvider extends ServiceProvider
|
|||||||
Gate::policy(Heir::class, HeirPolicy::class);
|
Gate::policy(Heir::class, HeirPolicy::class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected function registerRateLimiters(): void
|
||||||
|
{
|
||||||
|
RateLimiter::for('public-member-profile', function ($request) {
|
||||||
|
return Limit::perMinute(60)->by($request->ip());
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Register commands in the format of Command::class
|
* Register commands in the format of Command::class
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -0,0 +1,166 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ms">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body {
|
||||||
|
width: 840px;
|
||||||
|
height: 540px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
width: 840px;
|
||||||
|
height: 540px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
color: #eff6ff;
|
||||||
|
background: linear-gradient(to bottom right, #1e3a8a, #1e40af, #2563eb);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
|
outline: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 24px;
|
||||||
|
}
|
||||||
|
.orb-1 {
|
||||||
|
position: absolute;
|
||||||
|
right: -48px;
|
||||||
|
top: -48px;
|
||||||
|
width: 176px;
|
||||||
|
height: 176px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.orb-2 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -64px;
|
||||||
|
left: -40px;
|
||||||
|
width: 192px;
|
||||||
|
height: 192px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 8px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 36px;
|
||||||
|
width: auto;
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.18em;
|
||||||
|
opacity: 0.75;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
align-items: center;
|
||||||
|
gap: 24px;
|
||||||
|
margin-top: 20px;
|
||||||
|
}
|
||||||
|
.qr-wrap {
|
||||||
|
flex-shrink: 0;
|
||||||
|
border-radius: 8px;
|
||||||
|
background: #ffffff;
|
||||||
|
padding: 12px;
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.qr-wrap img {
|
||||||
|
display: block;
|
||||||
|
width: 144px;
|
||||||
|
height: 144px;
|
||||||
|
}
|
||||||
|
.info {
|
||||||
|
display: flex;
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 20px;
|
||||||
|
}
|
||||||
|
.info p {
|
||||||
|
font-size: 18px;
|
||||||
|
line-height: 1.375;
|
||||||
|
opacity: 0.85;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
.member-number {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 36px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
flex-shrink: 0;
|
||||||
|
margin-top: 16px;
|
||||||
|
padding-top: 12px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
text-align: center;
|
||||||
|
}
|
||||||
|
.footer p {
|
||||||
|
font-size: 12px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
opacity: 0.5;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<div class="orb-1"></div>
|
||||||
|
<div class="orb-2"></div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">
|
||||||
|
@if ($logoDataUri)
|
||||||
|
<img src="{{ $logoDataUri }}" alt="" class="logo">
|
||||||
|
@endif
|
||||||
|
<div class="badge">Belakang · Kod QR</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<div class="qr-wrap">
|
||||||
|
<img src="{{ $qrDataUri }}" alt="Kod QR profil anggota">
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="info">
|
||||||
|
<p>Imbas untuk sahkan profil anggota MyKOPKB.</p>
|
||||||
|
|
||||||
|
<div>
|
||||||
|
<div class="label">No. Anggota</div>
|
||||||
|
<div class="member-number">{{ $memberNumber }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<p>Koperasi Permodalan Kelantan Berhad (KOPKB)</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -0,0 +1,214 @@
|
|||||||
|
<!DOCTYPE html>
|
||||||
|
<html lang="ms">
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<style>
|
||||||
|
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||||
|
html, body {
|
||||||
|
width: 840px;
|
||||||
|
height: 540px;
|
||||||
|
overflow: hidden;
|
||||||
|
font-family: ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
||||||
|
}
|
||||||
|
.card {
|
||||||
|
position: relative;
|
||||||
|
width: 840px;
|
||||||
|
height: 540px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 16px;
|
||||||
|
color: #eff6ff;
|
||||||
|
background: linear-gradient(to bottom right, #1e3a8a, #1e40af, #1d4ed8);
|
||||||
|
box-shadow: 0 10px 15px -3px rgba(0, 0, 0, 0.1), 0 4px 6px -4px rgba(0, 0, 0, 0.1);
|
||||||
|
outline: 1px solid rgba(255, 255, 255, 0.2);
|
||||||
|
padding: 32px;
|
||||||
|
}
|
||||||
|
.orb-1 {
|
||||||
|
position: absolute;
|
||||||
|
right: -40px;
|
||||||
|
top: -40px;
|
||||||
|
width: 192px;
|
||||||
|
height: 192px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
}
|
||||||
|
.orb-2 {
|
||||||
|
position: absolute;
|
||||||
|
bottom: -48px;
|
||||||
|
left: -32px;
|
||||||
|
width: 208px;
|
||||||
|
height: 208px;
|
||||||
|
border-radius: 9999px;
|
||||||
|
background: rgba(255, 255, 255, 0.05);
|
||||||
|
}
|
||||||
|
.avatar {
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
right: 24px;
|
||||||
|
transform: translateY(-50%);
|
||||||
|
width: 112px;
|
||||||
|
height: 112px;
|
||||||
|
overflow: hidden;
|
||||||
|
border-radius: 8px;
|
||||||
|
border: 1px solid rgba(255, 255, 255, 0.25);
|
||||||
|
background: rgba(255, 255, 255, 0.1);
|
||||||
|
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
|
||||||
|
}
|
||||||
|
.avatar img {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
.avatar-fallback {
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
background: rgba(255, 255, 255, 0.15);
|
||||||
|
font-size: 18px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
text-transform: uppercase;
|
||||||
|
}
|
||||||
|
.content {
|
||||||
|
position: relative;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
height: 100%;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
.header {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-start;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
}
|
||||||
|
.logo {
|
||||||
|
height: 40px;
|
||||||
|
width: auto;
|
||||||
|
filter: brightness(0) invert(1);
|
||||||
|
}
|
||||||
|
.badge {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 600;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.2em;
|
||||||
|
opacity: 0.8;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
.main {
|
||||||
|
display: flex;
|
||||||
|
flex: 1;
|
||||||
|
min-height: 0;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
gap: 16px;
|
||||||
|
padding: 8px 0;
|
||||||
|
}
|
||||||
|
.label {
|
||||||
|
font-size: 14px;
|
||||||
|
font-weight: 500;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.1em;
|
||||||
|
opacity: 0.7;
|
||||||
|
}
|
||||||
|
.member-number {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-family: ui-monospace, SFMono-Regular, Menlo, Monaco, Consolas, monospace;
|
||||||
|
font-size: 48px;
|
||||||
|
font-weight: 600;
|
||||||
|
letter-spacing: 0.15em;
|
||||||
|
}
|
||||||
|
.company-block {
|
||||||
|
min-width: 0;
|
||||||
|
padding-right: 152px;
|
||||||
|
}
|
||||||
|
.company-value {
|
||||||
|
font-size: 20px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.footer {
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 12px;
|
||||||
|
flex-shrink: 0;
|
||||||
|
padding-top: 16px;
|
||||||
|
border-top: 1px solid rgba(255, 255, 255, 0.15);
|
||||||
|
}
|
||||||
|
.footer-name {
|
||||||
|
min-width: 0;
|
||||||
|
flex: 1;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 500;
|
||||||
|
white-space: nowrap;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
}
|
||||||
|
.footer-type {
|
||||||
|
flex-shrink: 0;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 24px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
.footer-sub {
|
||||||
|
margin-top: 2px;
|
||||||
|
font-size: 14px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
opacity: 0.6;
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<div class="card">
|
||||||
|
<div class="orb-1"></div>
|
||||||
|
<div class="orb-2"></div>
|
||||||
|
|
||||||
|
<div class="avatar">
|
||||||
|
@if ($avatarDataUri)
|
||||||
|
<img src="{{ $avatarDataUri }}" alt="{{ $memberName }}">
|
||||||
|
@else
|
||||||
|
<div class="avatar-fallback">{{ $avatarInitials }}</div>
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="content">
|
||||||
|
<div class="header">
|
||||||
|
@if ($logoDataUri)
|
||||||
|
<img src="{{ $logoDataUri }}" alt="" class="logo">
|
||||||
|
@endif
|
||||||
|
<div class="badge">Kad Digital</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="main">
|
||||||
|
<div>
|
||||||
|
<div class="label">No. Anggota</div>
|
||||||
|
<div class="member-number">{{ $memberNumber }}</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="company-block">
|
||||||
|
<div class="label">Unit</div>
|
||||||
|
<div class="company-value">{{ $companyName }}</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<div class="footer">
|
||||||
|
<div>
|
||||||
|
<div class="footer-name">{{ $memberName }}</div>
|
||||||
|
<div class="footer-sub">Nama</div>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<div class="footer-type">{{ $memberType }}</div>
|
||||||
|
<div class="footer-sub">Jenis Anggota</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</body>
|
||||||
|
</html>
|
||||||
@@ -8,6 +8,14 @@ use Modules\User\Http\Controllers\EmploymentController;
|
|||||||
use Modules\User\Http\Controllers\BankController;
|
use Modules\User\Http\Controllers\BankController;
|
||||||
use Modules\User\Http\Controllers\BankDetailController;
|
use Modules\User\Http\Controllers\BankDetailController;
|
||||||
use Modules\User\Http\Controllers\HeirController;
|
use Modules\User\Http\Controllers\HeirController;
|
||||||
|
use Modules\User\Http\Controllers\MemberDigitalCardController;
|
||||||
|
use Modules\User\Http\Controllers\PublicMemberProfileController;
|
||||||
|
|
||||||
|
Route::prefix('v1/public')->group(function () {
|
||||||
|
Route::middleware('throttle:public-member-profile')
|
||||||
|
->get('members/{token}', [PublicMemberProfileController::class, 'show'])
|
||||||
|
->name('user.public.member-profile');
|
||||||
|
});
|
||||||
|
|
||||||
Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(function () {
|
Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(function () {
|
||||||
Route::get('users/deleted', [UserController::class, 'deletedIndex'])->name('users.deleted.index');
|
Route::get('users/deleted', [UserController::class, 'deletedIndex'])->name('users.deleted.index');
|
||||||
@@ -27,7 +35,10 @@ Route::middleware(['auth:sanctum', 'single.session'])->prefix('v1')->group(funct
|
|||||||
|
|
||||||
// User profile management routes
|
// User profile management routes
|
||||||
Route::post('/profile', [UserController::class, 'updateProfile']);
|
Route::post('/profile', [UserController::class, 'updateProfile']);
|
||||||
|
Route::post('/profile/onboarding/complete', [UserController::class, 'completeOnboarding']);
|
||||||
Route::put('/profile/password', [UserController::class, 'updatePassword']);
|
Route::put('/profile/password', [UserController::class, 'updatePassword']);
|
||||||
|
Route::get('/profile/digital-card', [MemberDigitalCardController::class, 'download'])
|
||||||
|
->name('profile.digital-card.download');
|
||||||
|
|
||||||
// Impersonation routes
|
// Impersonation routes
|
||||||
Route::get('/impersonate/take/{id}', [ImpersonateController::class, 'take'])->name('impersonate');
|
Route::get('/impersonate/take/{id}', [ImpersonateController::class, 'take'])->name('impersonate');
|
||||||
|
|||||||
@@ -0,0 +1,141 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Services;
|
||||||
|
|
||||||
|
use BaconQrCode\Renderer\Image\SvgImageBackEnd;
|
||||||
|
use BaconQrCode\Renderer\ImageRenderer;
|
||||||
|
use BaconQrCode\Renderer\RendererStyle\RendererStyle;
|
||||||
|
use BaconQrCode\Writer;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Facades\View;
|
||||||
|
use Modules\Auth\Entities\User;
|
||||||
|
use Spatie\Browsershot\Browsershot;
|
||||||
|
|
||||||
|
class MemberDigitalCardService
|
||||||
|
{
|
||||||
|
public const CARD_WIDTH = 840;
|
||||||
|
|
||||||
|
public const CARD_HEIGHT = 540;
|
||||||
|
|
||||||
|
public function renderPng(User $user, string $side): string
|
||||||
|
{
|
||||||
|
$view = $side === 'belakang'
|
||||||
|
? 'user::digital-card.back'
|
||||||
|
: 'user::digital-card.front';
|
||||||
|
|
||||||
|
$html = View::make($view, $this->prepareViewData($user))->render();
|
||||||
|
|
||||||
|
return $this->makeBrowsershot($html)->screenshot();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, mixed>
|
||||||
|
*/
|
||||||
|
protected function prepareViewData(User $user): array
|
||||||
|
{
|
||||||
|
$user = app(PublicMemberProfileService::class)->loadVerifiableRelations($user);
|
||||||
|
$user->ensurePublicProfileToken();
|
||||||
|
|
||||||
|
$currentEmployment = $user->employments->firstWhere('is_current', true)
|
||||||
|
?? $user->employments->first();
|
||||||
|
|
||||||
|
$logoPath = config('user.digital_card_logo_path');
|
||||||
|
$profileUrl = rtrim(config('user.frontend_url'), '/').'/v/'.$user->public_profile_token;
|
||||||
|
|
||||||
|
return [
|
||||||
|
'memberNumber' => $this->displayCardValue($user->member_number),
|
||||||
|
'memberName' => $user->name ?: '-',
|
||||||
|
'memberType' => $this->displayCardValue($user->member_type),
|
||||||
|
'companyName' => $this->displayCardValue($currentEmployment?->company_name),
|
||||||
|
'avatarDataUri' => $this->resolveStorageDataUri($user->image_url),
|
||||||
|
'avatarInitials' => $this->avatarInitials($user->name),
|
||||||
|
'logoDataUri' => $this->resolveFileDataUri($logoPath),
|
||||||
|
'profileUrl' => $profileUrl,
|
||||||
|
'qrDataUri' => $this->generateQrDataUri($profileUrl),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function makeBrowsershot(string $html): Browsershot
|
||||||
|
{
|
||||||
|
$browsershot = Browsershot::html($html)
|
||||||
|
->setNodeModulePath(config('browsershot.node_module_path'))
|
||||||
|
->timeout(config('browsershot.timeout'))
|
||||||
|
->windowSize(self::CARD_WIDTH, self::CARD_HEIGHT)
|
||||||
|
->deviceScaleFactor(2)
|
||||||
|
->showBackground();
|
||||||
|
|
||||||
|
if ($nodeBinary = config('browsershot.node_binary')) {
|
||||||
|
$browsershot->setNodeBinary($nodeBinary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($npmBinary = config('browsershot.npm_binary')) {
|
||||||
|
$browsershot->setNpmBinary($npmBinary);
|
||||||
|
}
|
||||||
|
|
||||||
|
if ($chromePath = config('browsershot.chrome_path')) {
|
||||||
|
$browsershot->setChromePath($chromePath);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (config('browsershot.no_sandbox')) {
|
||||||
|
$browsershot->noSandbox();
|
||||||
|
}
|
||||||
|
|
||||||
|
return $browsershot;
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function generateQrDataUri(string $content): string
|
||||||
|
{
|
||||||
|
$renderer = new ImageRenderer(
|
||||||
|
new RendererStyle(220, 1),
|
||||||
|
new SvgImageBackEnd
|
||||||
|
);
|
||||||
|
$writer = new Writer($renderer);
|
||||||
|
$svg = $writer->writeString($content);
|
||||||
|
|
||||||
|
return 'data:image/svg+xml;base64,'.base64_encode($svg);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveFileDataUri(?string $path): ?string
|
||||||
|
{
|
||||||
|
if (empty($path) || ! file_exists($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$mime = mime_content_type($path) ?: 'image/svg+xml';
|
||||||
|
|
||||||
|
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($path));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function resolveStorageDataUri(?string $path): ?string
|
||||||
|
{
|
||||||
|
if (blank($path)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
$fullPath = Storage::disk('public')->path($path);
|
||||||
|
|
||||||
|
return $this->resolveFileDataUri($fullPath);
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function avatarInitials(?string $name): string
|
||||||
|
{
|
||||||
|
$trimmed = trim((string) $name);
|
||||||
|
|
||||||
|
if ($trimmed === '') {
|
||||||
|
return '--';
|
||||||
|
}
|
||||||
|
|
||||||
|
return strtoupper(mb_substr($trimmed, 0, 2));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function displayCardValue(mixed $value): string
|
||||||
|
{
|
||||||
|
if ($value === null || $value === '') {
|
||||||
|
return '-';
|
||||||
|
}
|
||||||
|
|
||||||
|
$trimmed = trim((string) $value);
|
||||||
|
|
||||||
|
return $trimmed !== '' ? $trimmed : '-';
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Services;
|
||||||
|
|
||||||
|
use Modules\Auth\Entities\User;
|
||||||
|
|
||||||
|
class PublicMemberProfileService
|
||||||
|
{
|
||||||
|
public function findMemberByToken(string $token): ?User
|
||||||
|
{
|
||||||
|
return User::query()
|
||||||
|
->where('public_profile_token', $token)
|
||||||
|
->where('status', 'active')
|
||||||
|
->first();
|
||||||
|
}
|
||||||
|
|
||||||
|
public function loadVerifiableRelations(User $member): User
|
||||||
|
{
|
||||||
|
$member->load(['employments' => function ($query) {
|
||||||
|
$query->orderByDesc('is_current')->orderByDesc('start_date');
|
||||||
|
}]);
|
||||||
|
|
||||||
|
return $member;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -121,6 +121,17 @@ class UserService
|
|||||||
/**
|
/**
|
||||||
* @return array{}|array{error: string}
|
* @return array{}|array{error: string}
|
||||||
*/
|
*/
|
||||||
|
public function completeOnboarding(User $user): User
|
||||||
|
{
|
||||||
|
if ($user->onboarding_completed_at === null) {
|
||||||
|
$user->update(['onboarding_completed_at' => now()]);
|
||||||
|
}
|
||||||
|
|
||||||
|
$user->load(['roles.permissions']);
|
||||||
|
|
||||||
|
return $user;
|
||||||
|
}
|
||||||
|
|
||||||
public function updatePassword(User $user, string $currentPassword, string $newPassword): array
|
public function updatePassword(User $user, string $currentPassword, string $newPassword): array
|
||||||
{
|
{
|
||||||
if (! Hash::check($currentPassword, $user->password)) {
|
if (! Hash::check($currentPassword, $user->password)) {
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
<?php
|
||||||
|
|
||||||
|
namespace Modules\User\Transformers;
|
||||||
|
|
||||||
|
use Illuminate\Http\Request;
|
||||||
|
use Illuminate\Http\Resources\Json\JsonResource;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
|
||||||
|
class PublicMemberProfileResource extends JsonResource
|
||||||
|
{
|
||||||
|
public function toArray(Request $request): array
|
||||||
|
{
|
||||||
|
$currentEmployment = $this->employments->firstWhere('is_current', true)
|
||||||
|
?? $this->employments->first();
|
||||||
|
|
||||||
|
return [
|
||||||
|
'name' => $this->name,
|
||||||
|
'member_number' => $this->member_number,
|
||||||
|
'member_type' => $this->member_type,
|
||||||
|
'status' => $this->status,
|
||||||
|
'image_url' => $this->image_url ? Storage::disk('public')->url($this->image_url) : null,
|
||||||
|
'company_name' => $currentEmployment?->company_name,
|
||||||
|
'verified_at' => now()->toIso8601String(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -56,11 +56,14 @@ class LetterService
|
|||||||
public function prepareViewData(array $overrides = []): array
|
public function prepareViewData(array $overrides = []): array
|
||||||
{
|
{
|
||||||
$logoPath = $overrides['logoPath'] ?? config('mail.logo_path');
|
$logoPath = $overrides['logoPath'] ?? config('mail.logo_path');
|
||||||
|
$signaturePath = $overrides['signaturePath'] ?? config('letter.signature_path');
|
||||||
|
|
||||||
return array_merge([
|
return array_merge([
|
||||||
'letterLayout' => LetterLayout::resolve(),
|
'letterLayout' => LetterLayout::resolve(),
|
||||||
'logoPath' => $logoPath,
|
'logoPath' => $logoPath,
|
||||||
'logoUrl' => $this->resolveLogoDataUri($logoPath),
|
'logoUrl' => $this->resolveImageDataUri($logoPath),
|
||||||
|
'signaturePath' => $signaturePath,
|
||||||
|
'signatureUrl' => $this->resolveImageDataUri($signaturePath),
|
||||||
'organizationAddress' => $overrides['organizationAddress'] ?? null,
|
'organizationAddress' => $overrides['organizationAddress'] ?? null,
|
||||||
'organizationContact' => $overrides['organizationContact'] ?? null,
|
'organizationContact' => $overrides['organizationContact'] ?? null,
|
||||||
], $overrides);
|
], $overrides);
|
||||||
@@ -97,14 +100,14 @@ class LetterService
|
|||||||
return $browsershot;
|
return $browsershot;
|
||||||
}
|
}
|
||||||
|
|
||||||
protected function resolveLogoDataUri(?string $logoPath): ?string
|
protected function resolveImageDataUri(?string $path): ?string
|
||||||
{
|
{
|
||||||
if (empty($logoPath) || ! file_exists($logoPath)) {
|
if (empty($path) || ! file_exists($path)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
$mime = mime_content_type($logoPath) ?: 'image/svg+xml';
|
$mime = mime_content_type($path) ?: 'image/svg+xml';
|
||||||
|
|
||||||
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($logoPath));
|
return 'data:'.$mime.';base64,'.base64_encode(file_get_contents($path));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -82,4 +82,12 @@ return [
|
|||||||
'black' => '#000000',
|
'black' => '#000000',
|
||||||
],
|
],
|
||||||
|
|
||||||
|
/*
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
| Assets
|
||||||
|
|--------------------------------------------------------------------------
|
||||||
|
*/
|
||||||
|
|
||||||
|
'signature_path' => resource_path('assets/signatures/signature.svg'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -124,6 +124,6 @@ return [
|
|||||||
|
|
|
|
||||||
*/
|
*/
|
||||||
|
|
||||||
'logo_path' => env('MAIL_LOGO_PATH', public_path('images/logo/logo-kopkb-letterhead.svg')),
|
'logo_path' => public_path('images/logo/logo.svg'),
|
||||||
|
|
||||||
];
|
];
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ return new class extends Migration
|
|||||||
public function up(): void
|
public function up(): void
|
||||||
{
|
{
|
||||||
Schema::create('jobs', function (Blueprint $table) {
|
Schema::create('jobs', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->id();
|
||||||
$table->string('queue')->index();
|
$table->string('queue')->index();
|
||||||
$table->longText('payload');
|
$table->longText('payload');
|
||||||
$table->unsignedTinyInteger('attempts');
|
$table->unsignedTinyInteger('attempts');
|
||||||
@@ -22,7 +22,7 @@ return new class extends Migration
|
|||||||
});
|
});
|
||||||
|
|
||||||
Schema::create('job_batches', function (Blueprint $table) {
|
Schema::create('job_batches', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->string('id')->primary();
|
||||||
$table->string('name');
|
$table->string('name');
|
||||||
$table->integer('total_jobs');
|
$table->integer('total_jobs');
|
||||||
$table->integer('pending_jobs');
|
$table->integer('pending_jobs');
|
||||||
@@ -35,7 +35,7 @@ return new class extends Migration
|
|||||||
});
|
});
|
||||||
|
|
||||||
Schema::create('failed_jobs', function (Blueprint $table) {
|
Schema::create('failed_jobs', function (Blueprint $table) {
|
||||||
$table->uuid('id')->primary();
|
$table->id();
|
||||||
$table->string('uuid')->unique();
|
$table->string('uuid')->unique();
|
||||||
$table->text('connection');
|
$table->text('connection');
|
||||||
$table->text('queue');
|
$table->text('queue');
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 275 KiB |
|
After Width: | Height: | Size: 202 KiB |
|
After Width: | Height: | Size: 7.8 KiB |
@@ -239,6 +239,12 @@
|
|||||||
height: var(--letter-signature-space-height);
|
height: var(--letter-signature-space-height);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.signature-space-img {
|
||||||
|
height: var(--letter-signature-space-height);
|
||||||
|
width: auto;
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
|
||||||
.signatory-name {
|
.signatory-name {
|
||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
text-transform: uppercase;
|
text-transform: uppercase;
|
||||||
|
|||||||
@@ -27,19 +27,19 @@
|
|||||||
</p>
|
</p>
|
||||||
<p style="line-height: 1;">
|
<p style="line-height: 1;">
|
||||||
2. Sukacita dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
2. Sukacita dimaklumkan bahawa, permohonan tuan untuk menjadi anggota Koperasi
|
||||||
Permodalan Kelantan Berhad(KoPKB) telah <strong>{{ membership_applications.board_result }}</strong> dalam {{ User manual fills before generate }}
|
Permodalan Kelantan Berhad(KoPKB) telah <strong></strong> dalam
|
||||||
</p>
|
</p>
|
||||||
<p style="line-height: 1;">
|
<p style="line-height: 1;">
|
||||||
3. Pihak Koperasi akan membuat potongan gaji melalui majikan tuan sebagaiman ketetapan berikut:-
|
3. Pihak Koperasi akan membuat potongan gaji melalui majikan tuan sebagaiman ketetapan berikut:-
|
||||||
<ul style="line-height: 1;">
|
<ul style="line-height: 1;">
|
||||||
<li>Saham</li>
|
<li>Saham</li>
|
||||||
<ul style="line-height: 1;">
|
<ul style="line-height: 1;">
|
||||||
<li>Potongan sebanyak RM 84.00 {{(6 months auto calculate)}}</li>
|
<li>Potongan sebanyak RM 84.00 </li>
|
||||||
<li>Potongan sebanyak RM {{ membership_application_applicants.stock_monthly_contribution }} sehingga Syer Maksima</li>
|
<li>Potongan sebanyak RM sehingga Syer Maksima</li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>Yuran</li>
|
<li>Yuran</li>
|
||||||
<ul style="line-height: 1;">
|
<ul style="line-height: 1;">
|
||||||
<li>Potongan sebanyak RM {{ membership_application_applicants.fee_monthly_contribution }} setiap bulan</li>
|
<li>Potongan sebanyak RM setiap bulan</li>
|
||||||
</ul>
|
</ul>
|
||||||
<li>Fi Masuk</li>
|
<li>Fi Masuk</li>
|
||||||
<ul style="line-height: 1;">
|
<ul style="line-height: 1;">
|
||||||
@@ -65,7 +65,11 @@
|
|||||||
@section('letter-signature')
|
@section('letter-signature')
|
||||||
<div class="signature-header">Dengan hormatnya,</div>
|
<div class="signature-header">Dengan hormatnya,</div>
|
||||||
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
<div class="signature-company">KOPERASI PERMODALAN KELANTAN BERHAD</div>
|
||||||
<div class="signature-space"></div>
|
<div class="signature-space">
|
||||||
|
@if (! empty($signatureUrl))
|
||||||
|
<img src="{{ $signatureUrl }}" alt="" class="signature-space-img">
|
||||||
|
@endif
|
||||||
|
</div>
|
||||||
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDDIN</div>
|
<div class="signatory-name">MUHAMMAD NAFIS BIN ZAINUDDIN</div>
|
||||||
<div class="signatory-title">Pengurus Besar</div>
|
<div class="signatory-title">Pengurus Besar</div>
|
||||||
<div class="signatory-title">b/p Setiausaha</div>
|
<div class="signatory-title">b/p Setiausaha</div>
|
||||||
|
|||||||
@@ -57,6 +57,7 @@
|
|||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
"maplibre-gl": "^5.18.0",
|
"maplibre-gl": "^5.18.0",
|
||||||
"pinia": "^3.0.4",
|
"pinia": "^3.0.4",
|
||||||
|
"qrcode": "^1.5.4",
|
||||||
"sweetalert2": "^11.26.25",
|
"sweetalert2": "^11.26.25",
|
||||||
"tailwind-merge": "^3.5.0",
|
"tailwind-merge": "^3.5.0",
|
||||||
"tailwindcss": "^4.1.18",
|
"tailwindcss": "^4.1.18",
|
||||||
@@ -71,6 +72,7 @@
|
|||||||
"@types/jsdom": "^27.0.0",
|
"@types/jsdom": "^27.0.0",
|
||||||
"@types/lodash": "^4.17.21",
|
"@types/lodash": "^4.17.21",
|
||||||
"@types/node": "^24.10.4",
|
"@types/node": "^24.10.4",
|
||||||
|
"@types/qrcode": "^1.5.6",
|
||||||
"@vitejs/plugin-vue": "^6.0.3",
|
"@vitejs/plugin-vue": "^6.0.3",
|
||||||
"@vitest/eslint-plugin": "^1.6.4",
|
"@vitest/eslint-plugin": "^1.6.4",
|
||||||
"@vue/eslint-config-prettier": "^10.2.0",
|
"@vue/eslint-config-prettier": "^10.2.0",
|
||||||
|
|||||||
@@ -119,6 +119,9 @@ importers:
|
|||||||
pinia:
|
pinia:
|
||||||
specifier: ^3.0.4
|
specifier: ^3.0.4
|
||||||
version: 3.0.4(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))
|
version: 3.0.4(typescript@5.9.3)(vue@3.5.35(typescript@5.9.3))
|
||||||
|
qrcode:
|
||||||
|
specifier: ^1.5.4
|
||||||
|
version: 1.5.4
|
||||||
sweetalert2:
|
sweetalert2:
|
||||||
specifier: ^11.26.25
|
specifier: ^11.26.25
|
||||||
version: 11.26.25
|
version: 11.26.25
|
||||||
@@ -156,6 +159,9 @@ importers:
|
|||||||
'@types/node':
|
'@types/node':
|
||||||
specifier: ^24.10.4
|
specifier: ^24.10.4
|
||||||
version: 24.12.4
|
version: 24.12.4
|
||||||
|
'@types/qrcode':
|
||||||
|
specifier: ^1.5.6
|
||||||
|
version: 1.5.6
|
||||||
'@vitejs/plugin-vue':
|
'@vitejs/plugin-vue':
|
||||||
specifier: ^6.0.3
|
specifier: ^6.0.3
|
||||||
version: 6.0.7(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.35(typescript@5.9.3))
|
version: 6.0.7(vite@7.3.5(@types/node@24.12.4)(jiti@2.7.0)(lightningcss@1.32.0))(vue@3.5.35(typescript@5.9.3))
|
||||||
@@ -1041,6 +1047,9 @@ packages:
|
|||||||
'@types/pako@2.0.4':
|
'@types/pako@2.0.4':
|
||||||
resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
|
resolution: {integrity: sha512-VWDCbrLeVXJM9fihYodcLiIv0ku+AlOa/TQ1SvYOaBuyrSKgEcro95LJyIsJ4vSo6BXIxOKxiJAat04CmST9Fw==}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
resolution: {integrity: sha512-te7NQcV2BOvdj2b1hCAHzAoMNuj65kNBMz0KBaxM6c3VGBOhU0dURQKOtH8CFNI/dsKkwlv32p26qYQTWoB5bw==}
|
||||||
|
|
||||||
'@types/raf@3.4.3':
|
'@types/raf@3.4.3':
|
||||||
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
|
resolution: {integrity: sha512-c4YAvMedbPZ5tEyxzQdMoOhhJ4RD3rngZIdwC2/qDN3d7JpEhB6fiBRKVY1lg5B7Wk+uPBjn5f39j1/2MY1oOw==}
|
||||||
|
|
||||||
@@ -1588,6 +1597,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
resolution: {integrity: sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
camelcase@5.3.1:
|
||||||
|
resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001793:
|
caniuse-lite@1.0.30001793:
|
||||||
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
|
resolution: {integrity: sha512-iwSsYWaCOoh26cV8NwNRViHlrfUvYsHDfRVcbtmw0Kg6PJIZZXwMkj1442FYLBGkeUf1juAsU3DTfxW579mrPA==}
|
||||||
|
|
||||||
@@ -1637,6 +1650,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
|
resolution: {integrity: sha512-xRwvIOMGrfOAnM1JYtqQImuaNtDEv9v6oIYAs4LIHwTiKee8uwvIi363igssOC0O5U04i4AlENs79LQLu9tEMw==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
resolution: {integrity: sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==}
|
||||||
|
|
||||||
clsx@2.1.1:
|
clsx@2.1.1:
|
||||||
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
@@ -1758,6 +1774,10 @@ packages:
|
|||||||
supports-color:
|
supports-color:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
decamelize@1.2.0:
|
||||||
|
resolution: {integrity: sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
decimal.js@10.6.0:
|
decimal.js@10.6.0:
|
||||||
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
resolution: {integrity: sha512-YpgQiITW3JXGntzdUmyUR1V812Hn8T1YVXhCu+wO3OpS4eU9l4YdD3qjyiKdV6mvV29zapkMeD390UVEf2lkUg==}
|
||||||
|
|
||||||
@@ -1784,6 +1804,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
resolution: {integrity: sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3:
|
||||||
|
resolution: {integrity: sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==}
|
||||||
|
|
||||||
dompurify@3.4.7:
|
dompurify@3.4.7:
|
||||||
resolution: {integrity: sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==}
|
resolution: {integrity: sha512-2jBxDJY4RR06tQNy4w5FlFH7kfxsQZlufd0sbv+chfHCxeJwrFw2baUDsSwvBISD4K4RDbd0PTfy3uNXsR6siA==}
|
||||||
|
|
||||||
@@ -2055,6 +2078,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
|
find-up@4.1.0:
|
||||||
|
resolution: {integrity: sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
resolution: {integrity: sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2109,6 +2136,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
resolution: {integrity: sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==}
|
||||||
engines: {node: '>=6.9.0'}
|
engines: {node: '>=6.9.0'}
|
||||||
|
|
||||||
|
get-caller-file@2.0.5:
|
||||||
|
resolution: {integrity: sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==}
|
||||||
|
engines: {node: 6.* || 8.* || >= 10.*}
|
||||||
|
|
||||||
get-east-asian-width@1.6.0:
|
get-east-asian-width@1.6.0:
|
||||||
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
|
resolution: {integrity: sha512-QRbvDIbx6YklUe6RxeTeleMR0yv3cYH6PsPZHcnVn7xv7zO1BHN8r0XETu8n6Ye3Q+ahtSarc3WgtNWmehIBfA==}
|
||||||
engines: {node: '>=18'}
|
engines: {node: '>=18'}
|
||||||
@@ -2501,6 +2532,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
|
resolution: {integrity: sha512-ME4Fb83LgEgwNw96RKNvKV4VTLuXfoKudAmm2lP8Kk87KaMK0/Xrx/aAkMWmT8mDb+3MlFDspfbCs7adjRxA2g==}
|
||||||
engines: {node: '>=20.0.0'}
|
engines: {node: '>=20.0.0'}
|
||||||
|
|
||||||
|
locate-path@5.0.0:
|
||||||
|
resolution: {integrity: sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
resolution: {integrity: sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -2679,14 +2714,26 @@ packages:
|
|||||||
ospath@1.2.2:
|
ospath@1.2.2:
|
||||||
resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
|
resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==}
|
||||||
|
|
||||||
|
p-limit@2.3.0:
|
||||||
|
resolution: {integrity: sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
resolution: {integrity: sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-locate@4.1.0:
|
||||||
|
resolution: {integrity: sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
resolution: {integrity: sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
|
|
||||||
|
p-try@2.2.0:
|
||||||
|
resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1:
|
package-json-from-dist@1.0.1:
|
||||||
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
|
||||||
|
|
||||||
@@ -2769,6 +2816,10 @@ packages:
|
|||||||
typescript:
|
typescript:
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
|
pngjs@5.0.0:
|
||||||
|
resolution: {integrity: sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
|
||||||
postcss-selector-parser@7.1.1:
|
postcss-selector-parser@7.1.1:
|
||||||
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
resolution: {integrity: sha512-orRsuYpJVw8LdAwqqLykBj9ecS5/cRHlI5+nvTo8LcCKmzDmqVORXtOIYEEQuL9D4BxtA1lm5isAqzQZCoQ6Eg==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2833,6 +2884,11 @@ packages:
|
|||||||
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
|
||||||
engines: {node: '>=6'}
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
resolution: {integrity: sha512-1ca71Zgiu6ORjHqFBDpnSMTR2ReToX4l1Au1VFLyVeBTFavzQnv5JxMFr3ukHVKpSrSA2MCk0lNJSykjUfz7Zg==}
|
||||||
|
engines: {node: '>=10.13.0'}
|
||||||
|
hasBin: true
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.2:
|
||||||
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
resolution: {integrity: sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==}
|
||||||
engines: {node: '>=0.6'}
|
engines: {node: '>=0.6'}
|
||||||
@@ -2856,10 +2912,17 @@ packages:
|
|||||||
request-progress@3.0.0:
|
request-progress@3.0.0:
|
||||||
resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==}
|
resolution: {integrity: sha512-MnWzEHHaxHO2iWiQuHrUPBi/1WeBf5PkxQqNyNvLl9VAYSdXkP8tQ3pBSeCPD+yw0v0Aq1zosWLz0BdeXpWwZg==}
|
||||||
|
|
||||||
|
require-directory@2.1.1:
|
||||||
|
resolution: {integrity: sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==}
|
||||||
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
require-from-string@2.0.2:
|
require-from-string@2.0.2:
|
||||||
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
resolution: {integrity: sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==}
|
||||||
engines: {node: '>=0.10.0'}
|
engines: {node: '>=0.10.0'}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0:
|
||||||
|
resolution: {integrity: sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==}
|
||||||
|
|
||||||
resolve-from@4.0.0:
|
resolve-from@4.0.0:
|
||||||
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
resolution: {integrity: sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==}
|
||||||
engines: {node: '>=4'}
|
engines: {node: '>=4'}
|
||||||
@@ -2916,6 +2979,9 @@ packages:
|
|||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
hasBin: true
|
hasBin: true
|
||||||
|
|
||||||
|
set-blocking@2.0.0:
|
||||||
|
resolution: {integrity: sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==}
|
||||||
engines: {node: '>=8'}
|
engines: {node: '>=8'}
|
||||||
@@ -3393,6 +3459,9 @@ packages:
|
|||||||
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
|
resolution: {integrity: sha512-2ytDk0kiEj/yu90JOAp44PVPUkO9+jVhyf+SybKlRHSDlvOOZhdPIrr7xTH64l4WixO2cP+wQIcgujkGBPPz6g==}
|
||||||
engines: {node: '>=20'}
|
engines: {node: '>=20'}
|
||||||
|
|
||||||
|
which-module@2.0.1:
|
||||||
|
resolution: {integrity: sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
resolution: {integrity: sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==}
|
||||||
engines: {node: '>= 8'}
|
engines: {node: '>= 8'}
|
||||||
@@ -3420,6 +3489,10 @@ packages:
|
|||||||
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
resolution: {integrity: sha512-OELeY0Q61OXpdUfTp+oweA/vtLVg5VDOXh+3he3PNzLGG/y0oylSOC1xRVj0+l4vQ3tj/bB1HVHv1ocXkQceFA==}
|
||||||
engines: {node: '>=0.8'}
|
engines: {node: '>=0.8'}
|
||||||
|
|
||||||
|
wrap-ansi@6.2.0:
|
||||||
|
resolution: {integrity: sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
|
||||||
engines: {node: '>=10'}
|
engines: {node: '>=10'}
|
||||||
@@ -3467,9 +3540,20 @@ packages:
|
|||||||
xmlchars@2.2.0:
|
xmlchars@2.2.0:
|
||||||
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
resolution: {integrity: sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==}
|
||||||
|
|
||||||
|
y18n@4.0.3:
|
||||||
|
resolution: {integrity: sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==}
|
||||||
|
|
||||||
yallist@3.1.1:
|
yallist@3.1.1:
|
||||||
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
resolution: {integrity: sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
resolution: {integrity: sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==}
|
||||||
|
engines: {node: '>=6'}
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
resolution: {integrity: sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==}
|
||||||
|
engines: {node: '>=8'}
|
||||||
|
|
||||||
yauzl@3.3.2:
|
yauzl@3.3.2:
|
||||||
resolution: {integrity: sha512-Md9ankxxN23wncAN8s7+Tn3Co52zLUPMtnrLAbVCnfG5d2tKBFfmygYSgXlqFgXObtzIgqkx7aNgDBpso9+4qA==}
|
resolution: {integrity: sha512-Md9ankxxN23wncAN8s7+Tn3Co52zLUPMtnrLAbVCnfG5d2tKBFfmygYSgXlqFgXObtzIgqkx7aNgDBpso9+4qA==}
|
||||||
engines: {node: '>=12'}
|
engines: {node: '>=12'}
|
||||||
@@ -4215,6 +4299,10 @@ snapshots:
|
|||||||
|
|
||||||
'@types/pako@2.0.4': {}
|
'@types/pako@2.0.4': {}
|
||||||
|
|
||||||
|
'@types/qrcode@1.5.6':
|
||||||
|
dependencies:
|
||||||
|
'@types/node': 24.12.4
|
||||||
|
|
||||||
'@types/raf@3.4.3':
|
'@types/raf@3.4.3':
|
||||||
optional: true
|
optional: true
|
||||||
|
|
||||||
@@ -4978,6 +5066,8 @@ snapshots:
|
|||||||
|
|
||||||
callsites@3.1.0: {}
|
callsites@3.1.0: {}
|
||||||
|
|
||||||
|
camelcase@5.3.1: {}
|
||||||
|
|
||||||
caniuse-lite@1.0.30001793: {}
|
caniuse-lite@1.0.30001793: {}
|
||||||
|
|
||||||
canvg@3.0.11:
|
canvg@3.0.11:
|
||||||
@@ -5033,6 +5123,12 @@ snapshots:
|
|||||||
slice-ansi: 8.0.0
|
slice-ansi: 8.0.0
|
||||||
string-width: 8.2.1
|
string-width: 8.2.1
|
||||||
|
|
||||||
|
cliui@6.0.0:
|
||||||
|
dependencies:
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
wrap-ansi: 6.2.0
|
||||||
|
|
||||||
clsx@2.1.1: {}
|
clsx@2.1.1: {}
|
||||||
|
|
||||||
codepage@1.15.0: {}
|
codepage@1.15.0: {}
|
||||||
@@ -5172,6 +5268,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
supports-color: 8.1.1
|
supports-color: 8.1.1
|
||||||
|
|
||||||
|
decamelize@1.2.0: {}
|
||||||
|
|
||||||
decimal.js@10.6.0: {}
|
decimal.js@10.6.0: {}
|
||||||
|
|
||||||
deep-is@0.1.4: {}
|
deep-is@0.1.4: {}
|
||||||
@@ -5189,6 +5287,8 @@ snapshots:
|
|||||||
|
|
||||||
detect-libc@2.1.2: {}
|
detect-libc@2.1.2: {}
|
||||||
|
|
||||||
|
dijkstrajs@1.0.3: {}
|
||||||
|
|
||||||
dompurify@3.4.7:
|
dompurify@3.4.7:
|
||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
'@types/trusted-types': 2.0.7
|
'@types/trusted-types': 2.0.7
|
||||||
@@ -5505,6 +5605,11 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
to-regex-range: 5.0.1
|
to-regex-range: 5.0.1
|
||||||
|
|
||||||
|
find-up@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
locate-path: 5.0.0
|
||||||
|
path-exists: 4.0.0
|
||||||
|
|
||||||
find-up@5.0.0:
|
find-up@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
locate-path: 6.0.0
|
locate-path: 6.0.0
|
||||||
@@ -5554,6 +5659,8 @@ snapshots:
|
|||||||
|
|
||||||
gensync@1.0.0-beta.2: {}
|
gensync@1.0.0-beta.2: {}
|
||||||
|
|
||||||
|
get-caller-file@2.0.5: {}
|
||||||
|
|
||||||
get-east-asian-width@1.6.0: {}
|
get-east-asian-width@1.6.0: {}
|
||||||
|
|
||||||
get-intrinsic@1.3.0:
|
get-intrinsic@1.3.0:
|
||||||
@@ -5928,6 +6035,10 @@ snapshots:
|
|||||||
rfdc: 1.4.1
|
rfdc: 1.4.1
|
||||||
wrap-ansi: 9.0.2
|
wrap-ansi: 9.0.2
|
||||||
|
|
||||||
|
locate-path@5.0.0:
|
||||||
|
dependencies:
|
||||||
|
p-locate: 4.1.0
|
||||||
|
|
||||||
locate-path@6.0.0:
|
locate-path@6.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-locate: 5.0.0
|
p-locate: 5.0.0
|
||||||
@@ -6107,14 +6218,24 @@ snapshots:
|
|||||||
|
|
||||||
ospath@1.2.2: {}
|
ospath@1.2.2: {}
|
||||||
|
|
||||||
|
p-limit@2.3.0:
|
||||||
|
dependencies:
|
||||||
|
p-try: 2.2.0
|
||||||
|
|
||||||
p-limit@3.1.0:
|
p-limit@3.1.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
yocto-queue: 0.1.0
|
yocto-queue: 0.1.0
|
||||||
|
|
||||||
|
p-locate@4.1.0:
|
||||||
|
dependencies:
|
||||||
|
p-limit: 2.3.0
|
||||||
|
|
||||||
p-locate@5.0.0:
|
p-locate@5.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
p-limit: 3.1.0
|
p-limit: 3.1.0
|
||||||
|
|
||||||
|
p-try@2.2.0: {}
|
||||||
|
|
||||||
package-json-from-dist@1.0.1: {}
|
package-json-from-dist@1.0.1: {}
|
||||||
|
|
||||||
pako@2.1.0: {}
|
pako@2.1.0: {}
|
||||||
@@ -6177,6 +6298,8 @@ snapshots:
|
|||||||
optionalDependencies:
|
optionalDependencies:
|
||||||
typescript: 5.9.3
|
typescript: 5.9.3
|
||||||
|
|
||||||
|
pngjs@5.0.0: {}
|
||||||
|
|
||||||
postcss-selector-parser@7.1.1:
|
postcss-selector-parser@7.1.1:
|
||||||
dependencies:
|
dependencies:
|
||||||
cssesc: 3.0.0
|
cssesc: 3.0.0
|
||||||
@@ -6225,6 +6348,12 @@ snapshots:
|
|||||||
|
|
||||||
punycode@2.3.1: {}
|
punycode@2.3.1: {}
|
||||||
|
|
||||||
|
qrcode@1.5.4:
|
||||||
|
dependencies:
|
||||||
|
dijkstrajs: 1.0.3
|
||||||
|
pngjs: 5.0.0
|
||||||
|
yargs: 15.4.1
|
||||||
|
|
||||||
qs@6.15.2:
|
qs@6.15.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
side-channel: 1.1.0
|
side-channel: 1.1.0
|
||||||
@@ -6250,8 +6379,12 @@ snapshots:
|
|||||||
dependencies:
|
dependencies:
|
||||||
throttleit: 1.0.1
|
throttleit: 1.0.1
|
||||||
|
|
||||||
|
require-directory@2.1.1: {}
|
||||||
|
|
||||||
require-from-string@2.0.2: {}
|
require-from-string@2.0.2: {}
|
||||||
|
|
||||||
|
require-main-filename@2.0.0: {}
|
||||||
|
|
||||||
resolve-from@4.0.0: {}
|
resolve-from@4.0.0: {}
|
||||||
|
|
||||||
resolve-protobuf-schema@2.1.0:
|
resolve-protobuf-schema@2.1.0:
|
||||||
@@ -6323,6 +6456,8 @@ snapshots:
|
|||||||
|
|
||||||
semver@7.8.1: {}
|
semver@7.8.1: {}
|
||||||
|
|
||||||
|
set-blocking@2.0.0: {}
|
||||||
|
|
||||||
shebang-command@2.0.0:
|
shebang-command@2.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
shebang-regex: 3.0.0
|
shebang-regex: 3.0.0
|
||||||
@@ -6781,6 +6916,8 @@ snapshots:
|
|||||||
tr46: 6.0.0
|
tr46: 6.0.0
|
||||||
webidl-conversions: 8.0.1
|
webidl-conversions: 8.0.1
|
||||||
|
|
||||||
|
which-module@2.0.1: {}
|
||||||
|
|
||||||
which@2.0.2:
|
which@2.0.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
isexe: 2.0.0
|
isexe: 2.0.0
|
||||||
@@ -6800,6 +6937,12 @@ snapshots:
|
|||||||
|
|
||||||
word@0.3.0: {}
|
word@0.3.0: {}
|
||||||
|
|
||||||
|
wrap-ansi@6.2.0:
|
||||||
|
dependencies:
|
||||||
|
ansi-styles: 4.3.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
strip-ansi: 6.0.1
|
||||||
|
|
||||||
wrap-ansi@7.0.0:
|
wrap-ansi@7.0.0:
|
||||||
dependencies:
|
dependencies:
|
||||||
ansi-styles: 4.3.0
|
ansi-styles: 4.3.0
|
||||||
@@ -6843,8 +6986,29 @@ snapshots:
|
|||||||
|
|
||||||
xmlchars@2.2.0: {}
|
xmlchars@2.2.0: {}
|
||||||
|
|
||||||
|
y18n@4.0.3: {}
|
||||||
|
|
||||||
yallist@3.1.1: {}
|
yallist@3.1.1: {}
|
||||||
|
|
||||||
|
yargs-parser@18.1.3:
|
||||||
|
dependencies:
|
||||||
|
camelcase: 5.3.1
|
||||||
|
decamelize: 1.2.0
|
||||||
|
|
||||||
|
yargs@15.4.1:
|
||||||
|
dependencies:
|
||||||
|
cliui: 6.0.0
|
||||||
|
decamelize: 1.2.0
|
||||||
|
find-up: 4.1.0
|
||||||
|
get-caller-file: 2.0.5
|
||||||
|
require-directory: 2.1.1
|
||||||
|
require-main-filename: 2.0.0
|
||||||
|
set-blocking: 2.0.0
|
||||||
|
string-width: 4.2.3
|
||||||
|
which-module: 2.0.1
|
||||||
|
y18n: 4.0.3
|
||||||
|
yargs-parser: 18.1.3
|
||||||
|
|
||||||
yauzl@3.3.2:
|
yauzl@3.3.2:
|
||||||
dependencies:
|
dependencies:
|
||||||
pend: 1.2.0
|
pend: 1.2.0
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 275 KiB |
|
Before Width: | Height: | Size: 1.4 KiB After Width: | Height: | Size: 202 KiB |
|
Before Width: | Height: | Size: 33 KiB |
@@ -3,6 +3,7 @@ import { SheetRoot, SheetContent } from '@/components/ui/sheet'
|
|||||||
import { Lucide } from '@/components/ui/lucide'
|
import { Lucide } from '@/components/ui/lucide'
|
||||||
import { useColorSchemeStore, type ColorSchemes } from '@/stores/color-scheme'
|
import { useColorSchemeStore, type ColorSchemes } from '@/stores/color-scheme'
|
||||||
import { useDarkModeStore } from '@/stores/dark-mode'
|
import { useDarkModeStore } from '@/stores/dark-mode'
|
||||||
|
import { applyColorSchemeClass, applyDarkModeClass } from '@/utils/applyAppearance'
|
||||||
import { ref } from 'vue'
|
import { ref } from 'vue'
|
||||||
|
|
||||||
const themeSwitcherSheet = ref(false)
|
const themeSwitcherSheet = ref(false)
|
||||||
@@ -10,30 +11,19 @@ const setThemeSwitcherSheet = (value: boolean) => {
|
|||||||
themeSwitcherSheet.value = value
|
themeSwitcherSheet.value = value
|
||||||
}
|
}
|
||||||
|
|
||||||
const setColorSchemeClass = () => {
|
|
||||||
const el = document.querySelectorAll('html')[0]
|
|
||||||
el?.setAttribute('data-theme', useColorSchemeStore().colorSchemeValue)
|
|
||||||
if (useDarkModeStore().darkModeValue) el?.classList.add('dark')
|
|
||||||
}
|
|
||||||
const colorSchemeStore = useColorSchemeStore()
|
const colorSchemeStore = useColorSchemeStore()
|
||||||
const switchColorScheme = (colorScheme: ColorSchemes) => {
|
const switchColorScheme = (colorScheme: ColorSchemes) => {
|
||||||
useColorSchemeStore().setColorScheme(colorScheme)
|
useColorSchemeStore().setColorScheme(colorScheme)
|
||||||
setColorSchemeClass()
|
applyColorSchemeClass()
|
||||||
setThemeSwitcherSheet(false)
|
setThemeSwitcherSheet(false)
|
||||||
}
|
}
|
||||||
setColorSchemeClass()
|
|
||||||
|
|
||||||
const setDarkModeClass = () => {
|
|
||||||
const el = document.querySelectorAll('html')[0]
|
|
||||||
useDarkModeStore().darkModeValue ? el?.classList.add('dark') : el?.classList.remove('dark')
|
|
||||||
}
|
|
||||||
const darkModeStore = useDarkModeStore()
|
const darkModeStore = useDarkModeStore()
|
||||||
const switchDarkMode = (darkMode: boolean) => {
|
const switchDarkMode = (darkMode: boolean) => {
|
||||||
useDarkModeStore().setDarkMode(darkMode)
|
useDarkModeStore().setDarkMode(darkMode)
|
||||||
setDarkModeClass()
|
applyDarkModeClass()
|
||||||
setThemeSwitcherSheet(false)
|
setThemeSwitcherSheet(false)
|
||||||
}
|
}
|
||||||
setDarkModeClass()
|
|
||||||
|
|
||||||
const colorSchemes: Array<ColorSchemes> = ['default', '1', '2', '3', '4', '5']
|
const colorSchemes: Array<ColorSchemes> = ['default', '1', '2', '3', '4', '5']
|
||||||
|
|
||||||
|
|||||||
@@ -7,30 +7,28 @@ import { normalizeProps, useMachine } from "@zag-js/vue";
|
|||||||
import { computed, provide } from "vue";
|
import { computed, provide } from "vue";
|
||||||
import { paginationRoot } from "@mykopkb/core/styles/pagination.styles";
|
import { paginationRoot } from "@mykopkb/core/styles/pagination.styles";
|
||||||
|
|
||||||
const {
|
const props = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
count,
|
|
||||||
pageSize,
|
|
||||||
siblingCount,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(pagination.machine, {
|
const paginationId = crypto.randomUUID();
|
||||||
...props,
|
|
||||||
count,
|
const machineProps = computed(() => {
|
||||||
pageSize,
|
const { class: _class, asChild: _asChild, id: _id, ...rest } = props;
|
||||||
siblingCount,
|
|
||||||
id: crypto.randomUUID(),
|
return {
|
||||||
|
...rest,
|
||||||
|
id: paginationId,
|
||||||
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
|
const service = useMachine(pagination.machine, machineProps);
|
||||||
const api = computed(() => pagination.connect(service, normalizeProps));
|
const api = computed(() => pagination.connect(service, normalizeProps));
|
||||||
|
|
||||||
provide("paginationApi", api);
|
provide("paginationApi", api);
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
<template>
|
<template>
|
||||||
<Slot :class="cn(paginationRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
<Slot :class="cn(paginationRoot, props.class)" v-bind="{ ...$attrs, ...api.getRootProps() }">
|
||||||
<slot v-if="asChild" />
|
<slot v-if="props.asChild" />
|
||||||
<div v-else>
|
<div v-else>
|
||||||
<slot />
|
<slot />
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
export const paginationRoot = 'flex gap-1'
|
export const paginationRoot = 'flex gap-1'
|
||||||
export const paginationItem = [
|
export const paginationItem = [
|
||||||
'h-10 px-4 py-2 inline-flex items-center justify-center rounded-xl cursor-pointer hover:bg-foreground/5',
|
'h-10 min-w-10 px-3 py-2 inline-flex items-center justify-center rounded-xl cursor-pointer text-foreground hover:bg-foreground/5',
|
||||||
'data-[selected]:border data-[selected]:bg-background data-[selected]:border-foreground/10 data-[selected]:font-medium data-[selected]:shadow-md/5',
|
'data-[selected]:border data-[selected]:bg-primary/10 data-[selected]:border-primary/30 data-[selected]:text-primary data-[selected]:font-semibold data-[selected]:shadow-md/5',
|
||||||
'data-[disabled]:opacity-70',
|
'data-[disabled]:opacity-70',
|
||||||
]
|
]
|
||||||
export const paginationPrevTrigger = paginationItem
|
export const paginationPrevTrigger = paginationItem
|
||||||
|
|||||||
@@ -94,25 +94,29 @@ const props = withDefaults(defineProps<{
|
|||||||
items: TableItem[];
|
items: TableItem[];
|
||||||
loading?: boolean;
|
loading?: boolean;
|
||||||
itemsPerPage?: number;
|
itemsPerPage?: number;
|
||||||
page?: number;
|
|
||||||
itemKey?: string;
|
itemKey?: string;
|
||||||
pagination?: PaginationData | null;
|
pagination?: PaginationData | null;
|
||||||
showPagination?: boolean;
|
showPagination?: boolean;
|
||||||
currentSort?: SortConfig[];
|
currentSort?: SortConfig[];
|
||||||
exportable?: boolean;
|
exportable?: boolean;
|
||||||
exportFileName?: string;
|
exportFileName?: string;
|
||||||
|
/** When true, table body scrolls inside a max-height box instead of growing the page. */
|
||||||
|
scrollable?: boolean;
|
||||||
|
/** Max height of the scrollable table area (CSS value). */
|
||||||
|
maxHeight?: string;
|
||||||
}>(), {
|
}>(), {
|
||||||
items: () => [],
|
items: () => [],
|
||||||
headers: () => [],
|
headers: () => [],
|
||||||
loading: false,
|
loading: false,
|
||||||
itemsPerPage: 10,
|
itemsPerPage: 10,
|
||||||
page: 1,
|
|
||||||
itemKey: 'id',
|
itemKey: 'id',
|
||||||
pagination: null,
|
pagination: null,
|
||||||
showPagination: false,
|
showPagination: false,
|
||||||
currentSort: () => [],
|
currentSort: () => [],
|
||||||
exportable: false,
|
exportable: false,
|
||||||
exportFileName: 'table-data'
|
exportFileName: 'table-data',
|
||||||
|
scrollable: true,
|
||||||
|
maxHeight: 'min(70dvh, 640px)',
|
||||||
});
|
});
|
||||||
|
|
||||||
// Define model props
|
// Define model props
|
||||||
@@ -122,7 +126,6 @@ const itemsPerPageModel = defineModel<number>('items-per-page');
|
|||||||
// Emits
|
// Emits
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:sort-by': [value: SortConfig[]];
|
'update:sort-by': [value: SortConfig[]];
|
||||||
'update:page': [value: number];
|
|
||||||
'update:items-per-page': [value: number];
|
'update:items-per-page': [value: number];
|
||||||
'update:column-widths': [value: Record<string, number>];
|
'update:column-widths': [value: Record<string, number>];
|
||||||
'export-error': [error: Error];
|
'export-error': [error: Error];
|
||||||
@@ -449,7 +452,6 @@ const setItemsPerPageValue = (details: { value: string[] }) => {
|
|||||||
|
|
||||||
const handlePageChange = (details: { page: number }) => {
|
const handlePageChange = (details: { page: number }) => {
|
||||||
pageModel.value = details.page;
|
pageModel.value = details.page;
|
||||||
emit('update:page', details.page);
|
|
||||||
};
|
};
|
||||||
|
|
||||||
const getItemKey = (item: TableItem, index: number) => {
|
const getItemKey = (item: TableItem, index: number) => {
|
||||||
@@ -461,16 +463,18 @@ const currentPage = computed(
|
|||||||
() => pageModel.value ?? props.pagination?.current_page ?? 1,
|
() => pageModel.value ?? props.pagination?.current_page ?? 1,
|
||||||
);
|
);
|
||||||
|
|
||||||
const isFirstPage = computed(() => currentPage.value <= 1);
|
|
||||||
|
|
||||||
const isLastPage = computed(
|
|
||||||
() => currentPage.value >= (props.pagination?.last_page ?? 1),
|
|
||||||
);
|
|
||||||
|
|
||||||
const slots = useSlots();
|
const slots = useSlots();
|
||||||
|
|
||||||
const showTableToolbar = computed(() => props.exportable || !!slots.toolbar);
|
const showTableToolbar = computed(() => props.exportable || !!slots.toolbar);
|
||||||
|
|
||||||
|
const scrollContainerStyle = computed(() => {
|
||||||
|
if (!props.scrollable) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
return { maxHeight: props.maxHeight };
|
||||||
|
});
|
||||||
|
|
||||||
const recordSummary = computed(() => {
|
const recordSummary = computed(() => {
|
||||||
if (!props.pagination) return null;
|
if (!props.pagination) return null;
|
||||||
|
|
||||||
@@ -743,12 +747,20 @@ watch(sortBy, (newSort) => {
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div class="relative px-4 pt-3" :class="loading && 'pointer-events-none opacity-50'">
|
<div
|
||||||
<Table variant="boxed" class="custom-data-table border-separate border-spacing-y-2.5">
|
class="relative px-4 pt-3"
|
||||||
|
:class="[
|
||||||
|
loading && 'pointer-events-none opacity-50',
|
||||||
|
scrollable && 'data-table-scroll-area overflow-auto',
|
||||||
|
]"
|
||||||
|
:style="scrollContainerStyle"
|
||||||
|
>
|
||||||
|
<Table variant="boxed" :class="cn('custom-data-table border-separate border-spacing-y-2.5', !scrollable && 'overflow-hidden')">
|
||||||
<TableHeader>
|
<TableHeader>
|
||||||
<TableRow class="border-0 hover:bg-transparent">
|
<TableRow class="border-0 hover:bg-transparent">
|
||||||
<TableHead v-for="header in sortedHeaders" :key="header.key" :class="cn(
|
<TableHead v-for="header in sortedHeaders" :key="header.key" :class="cn(
|
||||||
'group/head relative bg-primary text-primary-foreground font-semibold border-y border-primary/20 first:rounded-tl-xl first:border-s last:rounded-tr-xl last:border-e',
|
'group/head relative bg-primary text-primary-foreground font-semibold border-y border-primary/20 first:rounded-tl-xl first:border-s last:rounded-tr-xl last:border-e',
|
||||||
|
scrollable && 'sticky top-0 z-10',
|
||||||
headerAlignClass(header.align),
|
headerAlignClass(header.align),
|
||||||
)" :style="{ width: `${header.width}px`, minWidth: `${header.width}px` }">
|
)" :style="{ width: `${header.width}px`, minWidth: `${header.width}px` }">
|
||||||
<div v-if="header.sortable" class="flex items-center gap-2 cursor-pointer select-none" role="button"
|
<div v-if="header.sortable" class="flex items-center gap-2 cursor-pointer select-none" role="button"
|
||||||
@@ -851,28 +863,21 @@ watch(sortBy, (newSort) => {
|
|||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<PaginationRoot class="flex items-center gap-2" :count="pagination.total" :pageSize="pagination.per_page"
|
<PaginationRoot class="flex items-center gap-2" :count="pagination.total" :page-size="pagination.per_page"
|
||||||
:page="currentPage" :siblingCount="1" :onPageChange="handlePageChange">
|
:page="currentPage" :sibling-count="1" :on-page-change="handlePageChange">
|
||||||
<PaginationPrevTrigger asChild>
|
<PaginationPrevTrigger>
|
||||||
<Button size="sm" look="outline" variant="secondary" :disabled="isFirstPage">
|
<ArrowLeft class="size-4" />
|
||||||
<ArrowLeft class="size-4" />
|
|
||||||
</Button>
|
|
||||||
</PaginationPrevTrigger>
|
</PaginationPrevTrigger>
|
||||||
<PaginationContext v-slot="{ pagination: paginationApi }">
|
<PaginationContext v-slot="{ pagination: paginationApi }">
|
||||||
<template v-for="(pageItem, index) in paginationApi?.pages" :key="index">
|
<template v-for="(pageItem, index) in paginationApi?.pages" :key="index">
|
||||||
<PaginationItem v-if="pageItem.type === 'page'" v-bind="{ ...pageItem }" asChild>
|
<PaginationItem v-if="pageItem.type === 'page'" v-bind="{ ...pageItem }">
|
||||||
<Button size="sm" :look="pageItem.value === currentPage ? 'filled' : 'outline'"
|
{{ pageItem.value }}
|
||||||
:variant="pageItem.value === currentPage ? 'primary' : 'secondary'">
|
|
||||||
{{ pageItem.value }}
|
|
||||||
</Button>
|
|
||||||
</PaginationItem>
|
</PaginationItem>
|
||||||
<PaginationEllipsis v-else :index="index" />
|
<PaginationEllipsis v-else :index="index" />
|
||||||
</template>
|
</template>
|
||||||
</PaginationContext>
|
</PaginationContext>
|
||||||
<PaginationNextTrigger asChild>
|
<PaginationNextTrigger>
|
||||||
<Button size="sm" look="outline" variant="secondary" :disabled="isLastPage">
|
<ArrowRight class="size-4" />
|
||||||
<ArrowRight class="size-4" />
|
|
||||||
</Button>
|
|
||||||
</PaginationNextTrigger>
|
</PaginationNextTrigger>
|
||||||
</PaginationRoot>
|
</PaginationRoot>
|
||||||
</div>
|
</div>
|
||||||
@@ -881,8 +886,8 @@ watch(sortBy, (newSort) => {
|
|||||||
</template>
|
</template>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.custom-data-table {
|
.data-table-scroll-area > :deep(div) {
|
||||||
overflow: hidden;
|
overflow: visible;
|
||||||
}
|
}
|
||||||
|
|
||||||
.sortable-indicator {
|
.sortable-indicator {
|
||||||
|
|||||||
@@ -1,11 +0,0 @@
|
|||||||
<script setup lang="ts">
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
</script>
|
|
||||||
<template>
|
|
||||||
<Box class="w-full xl:w-1/2">
|
|
||||||
<div class="text-xl font-medium capitalize border-b border-foreground/15 pb-5">{{ $route.name }}</div>
|
|
||||||
<div class="mt-16 flex flex-col gap-10">
|
|
||||||
<RouterView />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
@@ -1,129 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
AccordionRoot,
|
|
||||||
AccordionItem,
|
|
||||||
AccordionTrigger,
|
|
||||||
AccordionContent,
|
|
||||||
} from "@/components/ui/accordion";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Box raised="single" class="w-full">
|
|
||||||
<AccordionRoot class="w-full" :default-value="['product-information']">
|
|
||||||
<AccordionItem value="product-information">
|
|
||||||
<AccordionTrigger>Product Information</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
Our flagship product combines cutting-edge technology with
|
|
||||||
sleek design. Built with premium materials, it offers
|
|
||||||
unparalleled performance and reliability.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Key features include advanced processing capabilities, and an
|
|
||||||
intuitive user interface designed for both beginners and
|
|
||||||
experts.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="shipping-details">
|
|
||||||
<AccordionTrigger>Shipping Details</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
We offer worldwide shipping through trusted courier partners.
|
|
||||||
Standard delivery takes 3-5 business days, while express
|
|
||||||
shipping ensures delivery within 1-2 business days.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
All orders are carefully packaged and fully insured. Track
|
|
||||||
your shipment in real-time through our dedicated tracking
|
|
||||||
portal.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="return-policy">
|
|
||||||
<AccordionTrigger>Return Policy</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
We stand behind our products with a comprehensive 30-day
|
|
||||||
return policy. If you're not completely satisfied, simply
|
|
||||||
return the item in its original condition.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Our hassle-free return process includes free return shipping
|
|
||||||
and full refunds processed within 48 hours of receiving the
|
|
||||||
returned item.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</AccordionRoot>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box raised="single" class="w-full">
|
|
||||||
<AccordionRoot class="w-full" :default-value="['product-information']">
|
|
||||||
<AccordionItem value="product-information">
|
|
||||||
<AccordionTrigger>Product Information</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
Our flagship product combines cutting-edge technology with
|
|
||||||
sleek design. Built with premium materials, it offers
|
|
||||||
unparalleled performance and reliability.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Key features include advanced processing capabilities, and an
|
|
||||||
intuitive user interface designed for both beginners and
|
|
||||||
experts.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="shipping-details">
|
|
||||||
<AccordionTrigger>Shipping Details</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
We offer worldwide shipping through trusted courier partners.
|
|
||||||
Standard delivery takes 3-5 business days, while express
|
|
||||||
shipping ensures delivery within 1-2 business days.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
All orders are carefully packaged and fully insured. Track
|
|
||||||
your shipment in real-time through our dedicated tracking
|
|
||||||
portal.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
<AccordionItem value="return-policy">
|
|
||||||
<AccordionTrigger>Return Policy</AccordionTrigger>
|
|
||||||
<AccordionContent>
|
|
||||||
<p class="mb-2">
|
|
||||||
We stand behind our products with a comprehensive 30-day
|
|
||||||
return policy. If you're not completely satisfied, simply
|
|
||||||
return the item in its original condition.
|
|
||||||
</p>
|
|
||||||
<p>
|
|
||||||
Our hassle-free return process includes free return shipping
|
|
||||||
and full refunds processed within 48 hours of receiving the
|
|
||||||
returned item.
|
|
||||||
</p>
|
|
||||||
</AccordionContent>
|
|
||||||
</AccordionItem>
|
|
||||||
</AccordionRoot>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,639 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
AlertRoot,
|
|
||||||
AlertTitle,
|
|
||||||
AlertDescription,
|
|
||||||
AlertCloseTrigger,
|
|
||||||
} from "@/components/ui/alert";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import { Compass } from "@lucide/vue";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/alert</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/alert/AlertCloseTrigger.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { X } from "@lucide/vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { alertCloseTrigger } from "@mykopkb/core/styles/alert.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const context = inject<{
|
|
||||||
present: boolean;
|
|
||||||
setPresent: (value: boolean) => void;
|
|
||||||
} | null>("alertPresent", null);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([className, alertCloseTrigger])" v-bind="{ ...props, ...$attrs }"
|
|
||||||
@click="context?.setPresent(false)">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<X v-else />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/alert/AlertDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { alertDescription } from "@mykopkb/core/styles/alert.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([className, alertDescription])" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/alert/AlertRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
alertRootVariants,
|
|
||||||
type AlertRootVariants,
|
|
||||||
} from "@mykopkb/core/styles/alert.styles";
|
|
||||||
import { Presence } from "@/components/ui/presence";
|
|
||||||
import { ref, provide } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
look,
|
|
||||||
variant,
|
|
||||||
...rest
|
|
||||||
} = defineProps<AlertRootVariants & { class?: string }>();
|
|
||||||
|
|
||||||
const present = ref(true);
|
|
||||||
const setPresent = (value: boolean) => {
|
|
||||||
present.value = value;
|
|
||||||
};
|
|
||||||
|
|
||||||
provide("alertPresent", { present, setPresent });
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Presence :class="
|
|
||||||
cn(
|
|
||||||
alertRootVariants({
|
|
||||||
look,
|
|
||||||
variant,
|
|
||||||
}),
|
|
||||||
className
|
|
||||||
)
|
|
||||||
" v-bind="rest" :present="present">
|
|
||||||
<slot />
|
|
||||||
</Presence>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/alert/AlertTitle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { alertTitle } from "@mykopkb/core/styles/alert.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([className, alertTitle])" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/alert/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as AlertRoot } from "./AlertRoot.vue";
|
|
||||||
export { default as AlertTitle } from "./AlertTitle.vue";
|
|
||||||
export { default as AlertDescription } from "./AlertDescription.vue";
|
|
||||||
export { default as AlertCloseTrigger } from "./AlertCloseTrigger.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
AlertRoot,
|
|
||||||
AlertTitle,
|
|
||||||
AlertDescription,
|
|
||||||
AlertCloseTrigger,
|
|
||||||
} from "@/components/ui/alert";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot>
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>
|
|
||||||
Success! Your changes have been saved
|
|
||||||
</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview class="flex-col!">
|
|
||||||
<template #preview>
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview class="flex-col!">
|
|
||||||
<template #preview>
|
|
||||||
<AlertRoot look="filled" variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot look="filled" variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview class="flex-col!">
|
|
||||||
<template #preview>
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview class="flex-col!">
|
|
||||||
<template #preview>
|
|
||||||
<AlertRoot look="filled" variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AlertRoot look="filled" variant="primary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="secondary">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="success">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="danger">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="pending">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
<AlertRoot look="filled" variant="warning">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
</AlertRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview class="flex-col!">
|
|
||||||
<template #preview>
|
|
||||||
<Box class="p-0">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
<Box class="p-0" raised="single">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
<Box class="p-0" raised="double">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box class="p-0">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
<Box class="p-0" raised="single">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
<Box class="p-0" raised="double">
|
|
||||||
<AlertRoot variant="ghost">
|
|
||||||
<Compass />
|
|
||||||
<AlertTitle>Success! Your changes have been saved</AlertTitle>
|
|
||||||
<AlertDescription>
|
|
||||||
This is an alert with icon, title and description.
|
|
||||||
</AlertDescription>
|
|
||||||
<AlertCloseTrigger />
|
|
||||||
</AlertRoot>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,239 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
AvatarRoot,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/components/ui/avatar";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<AvatarRoot>
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AvatarRoot>
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/avatar</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/avatar/AvatarFallback.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/avatar";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { avatarFallback } from "@mykopkb/core/styles/avatar.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
asChild?: boolean;
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("avatarApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(avatarFallback, className)" v-bind="{ ...props, ...$attrs, ...api?.getFallbackProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/avatar/AvatarImage.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { avatarImage } from "@mykopkb/core/styles/avatar.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import type { Api } from "@zag-js/avatar";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("avatarApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<img :class="cn(avatarImage, className)" v-bind="{ ...props, ...$attrs, ...api?.getImageProps() }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/avatar/AvatarRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as avatar from "@zag-js/avatar";
|
|
||||||
import { provide, computed } from "vue";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
import type { Props } from "@zag-js/avatar";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
avatarRootVariants,
|
|
||||||
type AvatarRootVariants,
|
|
||||||
} from "@mykopkb/core/styles/avatar.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
bordered,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
AvatarRootVariants &
|
|
||||||
Partial<Props> & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const service = useMachine(avatar.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const api = computed(() => avatar.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("avatarApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="
|
|
||||||
cn(
|
|
||||||
avatarRootVariants({
|
|
||||||
bordered,
|
|
||||||
className,
|
|
||||||
}),
|
|
||||||
className
|
|
||||||
)
|
|
||||||
" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/avatar/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as AvatarRoot } from "./AvatarRoot.vue";
|
|
||||||
export { default as AvatarFallback } from "./AvatarFallback.vue";
|
|
||||||
export { default as AvatarImage } from "./AvatarImage.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
AvatarRoot,
|
|
||||||
AvatarFallback,
|
|
||||||
AvatarImage,
|
|
||||||
} from "@/components/ui/avatar";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AvatarRoot>
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<AvatarRoot :bordered="false">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AvatarRoot :bordered="false">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<AvatarRoot class="rounded-full">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AvatarRoot class="rounded-full">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<AvatarRoot class="rounded-full" :bordered="false">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<AvatarRoot class="rounded-full" :bordered="false">
|
|
||||||
<AvatarFallback>PA</AvatarFallback>
|
|
||||||
<AvatarImage src="https://i.pravatar.cc/300" alt="avatar" />
|
|
||||||
</AvatarRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,351 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { ChevronDown, CheckSquare } from "@lucide/vue";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge variant="primary">12%</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge variant="primary">12%</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/badge/Badge.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
TooltipRoot,
|
|
||||||
TooltipTrigger,
|
|
||||||
TooltipPositioner,
|
|
||||||
TooltipContent,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
import {
|
|
||||||
badgeVariants,
|
|
||||||
type BadgeVariants,
|
|
||||||
} from "@mykopkb/core/styles/badge.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
look,
|
|
||||||
variant,
|
|
||||||
content,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
BadgeVariants & {
|
|
||||||
class?: string;
|
|
||||||
content?: string;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<TooltipRoot :disabled="!content">
|
|
||||||
<TooltipTrigger as-child>
|
|
||||||
<span :class="cn(badgeVariants({ look, variant, className }))" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</TooltipTrigger>
|
|
||||||
<TooltipPositioner>
|
|
||||||
<TooltipContent>\{\{ content \}\}</TooltipContent>
|
|
||||||
</TooltipPositioner>
|
|
||||||
</TooltipRoot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/badge/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Badge } from "./Badge.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge>12%</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge variant="primary">12%</Badge>
|
|
||||||
<Badge variant="secondary">12%</Badge>
|
|
||||||
<Badge variant="success">12%</Badge>
|
|
||||||
<Badge variant="danger">12%</Badge>
|
|
||||||
<Badge variant="pending">12%</Badge>
|
|
||||||
<Badge variant="warning">12%</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge variant="primary">12%</Badge>
|
|
||||||
<Badge variant="secondary">12%</Badge>
|
|
||||||
<Badge variant="success">12%</Badge>
|
|
||||||
<Badge variant="danger">12%</Badge>
|
|
||||||
<Badge variant="pending">12%</Badge>
|
|
||||||
<Badge variant="warning">12%</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge look="outline" variant="primary"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="secondary"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="success"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="danger"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="pending"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="warning"> 12% </Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge look="outline" variant="primary"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="secondary"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="success"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="danger"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="pending"> 12% </Badge>
|
|
||||||
<Badge look="outline" variant="warning"> 12% </Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge look="outline" variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge look="outline" variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge look="filled" variant="primary"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="secondary"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="success"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="danger"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="pending"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="warning"> 12% </Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge look="filled" variant="primary"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="secondary"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="success"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="danger"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="pending"> 12% </Badge>
|
|
||||||
<Badge look="filled" variant="warning"> 12% </Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge look="filled" variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge look="filled" variant="primary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="secondary"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="success"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="danger"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="pending"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
<Badge look="filled" variant="warning"> 12%
|
|
||||||
<ChevronDown />
|
|
||||||
</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Badge look="outline" variant="primary" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="secondary" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="success" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="danger" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="pending" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="warning" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Badge look="outline" variant="primary" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="secondary" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="success" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="danger" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="pending" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
<Badge look="outline" variant="warning" content="12% Higher than last month">
|
|
||||||
<CheckSquare /> 12%
|
|
||||||
</Badge>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,199 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { CircleGauge } from "@lucide/vue";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Box class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/box/box.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
boxVariants,
|
|
||||||
type BoxVariants,
|
|
||||||
} from "@mykopkb/core/styles/box.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
raised,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
BoxVariants & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(boxVariants({ raised, className }), className)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/box/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Box } from "./box.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Box class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Box raised="single" class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box raised="single" class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Box raised="double" class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Box raised="double" class="w-70">
|
|
||||||
<CircleGauge class="size-7 stroke-1 fill-foreground/10" />
|
|
||||||
<div class="mt-6 text-2xl font-medium leading-8">
|
|
||||||
$724,091.47
|
|
||||||
</div>
|
|
||||||
<div class="mt-1.5 text-xs uppercase opacity-70">
|
|
||||||
Item Sales
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,181 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Breadcrumb } from "@/components/ui/breadcrumb";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Breadcrumb :items="['Dashboard', 'Users', 'Admins', 'Settings', 'Edit Profile']" />
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Breadcrumb :items="['Dashboard', 'Users', 'Admins', 'Settings', 'Edit Profile']" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/breadcrumb/Breadcrumb.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { ChevronRight, Ellipsis } from "@lucide/vue";
|
|
||||||
import {
|
|
||||||
MenuRoot,
|
|
||||||
MenuTrigger,
|
|
||||||
MenuPositioner,
|
|
||||||
MenuContent,
|
|
||||||
MenuItem,
|
|
||||||
} from "@/components/ui/menu";
|
|
||||||
import { BreadcrumbItem, BreadcrumbLink, BreadcrumbList } from ".";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
items: string[];
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<nav aria-label="breadcrumb" data-slot="breadcrumb" v-bind="{ ...props, ...$attrs }" :class="cn(className)">
|
|
||||||
<BreadcrumbList>
|
|
||||||
<template v-if="items.length <= 3">
|
|
||||||
<template v-for="(item, key) in items">
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink>\{\{ item \}\}</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<ChevronRight v-if="key < items.length - 1" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
<template v-else>
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink>\{\{ items[0] \}\}</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<ChevronRight />
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<MenuRoot>
|
|
||||||
<MenuTrigger asChild>
|
|
||||||
<BreadcrumbLink>
|
|
||||||
<Ellipsis />
|
|
||||||
</BreadcrumbLink>
|
|
||||||
</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem v-for="(item, itemKey) in items.slice(1, -2)" :value="item" :key="itemKey">
|
|
||||||
\{\{ item \}\}
|
|
||||||
</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<ChevronRight />
|
|
||||||
<template v-for="(item, index) in items.slice(-2)">
|
|
||||||
<BreadcrumbItem>
|
|
||||||
<BreadcrumbLink>\{\{ item \}\}</BreadcrumbLink>
|
|
||||||
</BreadcrumbItem>
|
|
||||||
<ChevronRight v-if="index < 1" />
|
|
||||||
</template>
|
|
||||||
</template>
|
|
||||||
</BreadcrumbList>
|
|
||||||
</nav>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { breadcrumbItem } from "@mykopkb/core/styles/breadcrumb.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<li :class="cn(className, breadcrumbItem)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</li>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbLink.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { breadcrumbLink } from "@mykopkb/core/styles/breadcrumb.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<a :class="cn(className, breadcrumbLink)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</a>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/breadcrumb/BreadcrumbList.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { breadcrumbList } from "@mykopkb/core/styles/breadcrumb.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ol :class="cn(className, breadcrumbList)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</ol>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/breadcrumb/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Breadcrumb } from "./Breadcrumb.vue";
|
|
||||||
export { default as BreadcrumbItem } from "./BreadcrumbItem.vue";
|
|
||||||
export { default as BreadcrumbLink } from "./BreadcrumbLink.vue";
|
|
||||||
export { default as BreadcrumbList } from "./BreadcrumbList.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Breadcrumb } from "@/components/ui/breadcrumb";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Breadcrumb :items="[
|
|
||||||
'Dashboard',
|
|
||||||
'Users',
|
|
||||||
'Admins',
|
|
||||||
'Settings',
|
|
||||||
'Edit Profile',
|
|
||||||
]" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,445 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
CarouselRoot,
|
|
||||||
CarouselControl,
|
|
||||||
CarouselPrevTrigger,
|
|
||||||
CarouselNextTrigger,
|
|
||||||
CarouselIndicatorGroup,
|
|
||||||
CarouselIndicator,
|
|
||||||
CarouselItemGroup,
|
|
||||||
CarouselItem,
|
|
||||||
} from "@/components/ui/carousel";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
const images = Array.from(
|
|
||||||
{ length: 5 },
|
|
||||||
(_, i) => `https://picsum.photos/seed/${i + 1}/500/300`
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<CarouselRoot :default-page="0" :slide-count="images.length" class="size-72">
|
|
||||||
<CarouselControl>
|
|
||||||
<CarouselPrevTrigger />
|
|
||||||
<CarouselNextTrigger />
|
|
||||||
</CarouselControl>
|
|
||||||
<CarouselIndicatorGroup>
|
|
||||||
<CarouselIndicator v-for="(_, index) in images" :key="index" :index="index" />
|
|
||||||
</CarouselIndicatorGroup>
|
|
||||||
<CarouselItemGroup>
|
|
||||||
<CarouselItem v-for="(image, index) in images" :key="index" :index="index"
|
|
||||||
class="text-5xl bold flex items-center justify-center">
|
|
||||||
{{ index + 1 }}
|
|
||||||
</CarouselItem>
|
|
||||||
</CarouselItemGroup>
|
|
||||||
</CarouselRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
const images = Array.from(
|
|
||||||
{ length: 5 },
|
|
||||||
(_, i) => \`https://picsum.photos/seed/\${i + 1}/500/300\`
|
|
||||||
);
|
|
||||||
|
|
||||||
<CarouselRoot :default-page="0" :slide-count="images.length" class="size-72">
|
|
||||||
<CarouselControl>
|
|
||||||
<CarouselPrevTrigger />
|
|
||||||
<CarouselNextTrigger />
|
|
||||||
</CarouselControl>
|
|
||||||
<CarouselIndicatorGroup>
|
|
||||||
<CarouselIndicator v-for="(_, index) in images" :key="index" :index="index" />
|
|
||||||
</CarouselIndicatorGroup>
|
|
||||||
<CarouselItemGroup>
|
|
||||||
<CarouselItem v-for="(image, index) in images" :key="index" :index="index"
|
|
||||||
class="text-5xl bold flex items-center justify-center">
|
|
||||||
\{\{ index + 1 \}\}
|
|
||||||
</CarouselItem>
|
|
||||||
</CarouselItemGroup>
|
|
||||||
</CarouselRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/carousel</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as carousel from "@zag-js/carousel";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
import type { Props } from "@zag-js/carousel";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { carouselRoot } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
defaultPage,
|
|
||||||
slideCount,
|
|
||||||
spacing = "2rem",
|
|
||||||
allowMouseDrag = true,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { asChild?: boolean; class?: string }>();
|
|
||||||
|
|
||||||
const service = useMachine(carousel.machine, {
|
|
||||||
defaultPage,
|
|
||||||
slideCount,
|
|
||||||
spacing,
|
|
||||||
allowMouseDrag,
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const api = computed(() => carousel.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("carouselApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(carouselRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { carouselControl } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(carouselControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselPrevTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ArrowLeft } from "@lucide/vue";
|
|
||||||
import { carouselPrevTrigger } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getPrevTriggerProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Button variant="ghost" v-else :class="cn(carouselPrevTrigger, className)">
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<ArrowLeft v-else />
|
|
||||||
</Button>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselNextTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ArrowRight } from "@lucide/vue";
|
|
||||||
import { carouselNextTrigger } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getNextTriggerProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Button variant="ghost" v-else :class="cn(carouselNextTrigger, className)">
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<ArrowRight v-else />
|
|
||||||
</Button>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselIndicatorGroup.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { carouselIndicatorGroup } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(carouselIndicatorGroup, className)"
|
|
||||||
v-bind="{ ...props, ...$attrs, ...api?.getIndicatorGroupProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { carouselIndicator } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api, IndicatorProps } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
index,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
} & IndicatorProps
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<button :class="cn(carouselIndicator, className)"
|
|
||||||
v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps({ index }) }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselItemGroup.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { carouselItemGroup } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(carouselItemGroup, className)" v-bind="{ ...props, ...$attrs, ...api?.getItemGroupProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/CarouselItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { carouselItem } from "@mykopkb/core/styles/carousel.styles";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/carousel";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
index,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
} & ItemProps
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("carouselApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getItemProps({ index }) }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Box v-else :class="cn(carouselItem, className)">
|
|
||||||
<slot />
|
|
||||||
</Box>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/carousel/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as CarouselRoot } from "./CarouselRoot.vue";
|
|
||||||
export { default as CarouselControl } from "./CarouselControl.vue";
|
|
||||||
export { default as CarouselPrevTrigger } from "./CarouselPrevTrigger.vue";
|
|
||||||
export { default as CarouselNextTrigger } from "./CarouselNextTrigger.vue";
|
|
||||||
export { default as CarouselIndicatorGroup } from "./CarouselIndicatorGroup.vue";
|
|
||||||
export { default as CarouselIndicator } from "./CarouselIndicator.vue";
|
|
||||||
export { default as CarouselItemGroup } from "./CarouselItemGroup.vue";
|
|
||||||
export { default as CarouselItem } from "./CarouselItem.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
CarouselRoot,
|
|
||||||
CarouselControl,
|
|
||||||
CarouselPrevTrigger,
|
|
||||||
CarouselNextTrigger,
|
|
||||||
CarouselIndicatorGroup,
|
|
||||||
CarouselIndicator,
|
|
||||||
CarouselItemGroup,
|
|
||||||
CarouselItem,
|
|
||||||
} from "@/components/ui/carousel";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<CarouselRoot :default-page="0" :slide-count="images.length" class="size-72">
|
|
||||||
<CarouselControl>
|
|
||||||
<CarouselPrevTrigger />
|
|
||||||
<CarouselNextTrigger />
|
|
||||||
</CarouselControl>
|
|
||||||
<CarouselIndicatorGroup>
|
|
||||||
<CarouselIndicator v-for="(_, index) in images" :key="index" :index="index" />
|
|
||||||
</CarouselIndicatorGroup>
|
|
||||||
<CarouselItemGroup>
|
|
||||||
<CarouselItem v-for="(image, index) in images" :key="index" :index="index"
|
|
||||||
class="text-5xl bold flex items-center justify-center">
|
|
||||||
\{\{ index + 1 \}\}
|
|
||||||
</CarouselItem>
|
|
||||||
</CarouselItemGroup>
|
|
||||||
</CarouselRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<CarouselRoot :default-page="0" :slide-count="images.length" class="size-72">
|
|
||||||
<CarouselControl>
|
|
||||||
<CarouselPrevTrigger />
|
|
||||||
<CarouselNextTrigger />
|
|
||||||
</CarouselControl>
|
|
||||||
<CarouselIndicatorGroup>
|
|
||||||
<CarouselIndicator v-for="(_, index) in images" :key="index" :index="index" />
|
|
||||||
</CarouselIndicatorGroup>
|
|
||||||
<CarouselItemGroup>
|
|
||||||
<CarouselItem v-for="(image, index) in images" :key="index" :index="index">
|
|
||||||
<img :src="image" :alt="`Slide $\{index\}`" />
|
|
||||||
</CarouselItem>
|
|
||||||
</CarouselItemGroup>
|
|
||||||
</CarouselRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<CarouselRoot :default-page="0" :slide-count="images.length" class="size-72">
|
|
||||||
<CarouselControl>
|
|
||||||
<CarouselPrevTrigger />
|
|
||||||
<CarouselNextTrigger />
|
|
||||||
</CarouselControl>
|
|
||||||
<CarouselIndicatorGroup>
|
|
||||||
<CarouselIndicator v-for="(_, index) in images" :key="index" :index="index" />
|
|
||||||
</CarouselIndicatorGroup>
|
|
||||||
<CarouselItemGroup>
|
|
||||||
<CarouselItem v-for="(image, index) in images" :key="index" :index="index">
|
|
||||||
<img :src="image" :alt="\`Slide $\{index\}\`" />
|
|
||||||
</CarouselItem>
|
|
||||||
</CarouselItemGroup>
|
|
||||||
</CarouselRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Chart, getColor } from "@/components/ui/chart";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Chart class="max-w-100" :config="{
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: [
|
|
||||||
'Jan',
|
|
||||||
'Feb',
|
|
||||||
'Mar',
|
|
||||||
'Apr',
|
|
||||||
'May',
|
|
||||||
'Jun',
|
|
||||||
'Jul',
|
|
||||||
'Aug',
|
|
||||||
'Sep',
|
|
||||||
'Oct',
|
|
||||||
'Nov',
|
|
||||||
'Dec',
|
|
||||||
],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Html Template',
|
|
||||||
maxBarThickness: 12,
|
|
||||||
data: [
|
|
||||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
|
||||||
],
|
|
||||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
|
||||||
borderColor: () => getColor('--color-foreground'),
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}" />
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<Chart class="max-w-100" :config="{
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: [
|
|
||||||
'Jan',
|
|
||||||
'Feb',
|
|
||||||
'Mar',
|
|
||||||
'Apr',
|
|
||||||
'May',
|
|
||||||
'Jun',
|
|
||||||
'Jul',
|
|
||||||
'Aug',
|
|
||||||
'Sep',
|
|
||||||
'Oct',
|
|
||||||
'Nov',
|
|
||||||
'Dec',
|
|
||||||
],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Html Template',
|
|
||||||
maxBarThickness: 12,
|
|
||||||
data: [
|
|
||||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
|
||||||
],
|
|
||||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
|
||||||
borderColor: () => getColor('--color-foreground'),
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add chart.js</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/chart/Chart.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup generic="TType extends ChartType">
|
|
||||||
import ChartJs from "chart.js/auto";
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { chart } from "@mykopkb/core/styles/chart.styles";
|
|
||||||
import type { ChartType, ChartConfiguration } from "chart.js";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
config,
|
|
||||||
getRef,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
config: ChartConfiguration<TType>;
|
|
||||||
getRef?: (chart: ChartJs<TType>) => void;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const chartRef = ref<
|
|
||||||
| (HTMLCanvasElement & {
|
|
||||||
instance?: ChartJs<TType>;
|
|
||||||
})
|
|
||||||
| null
|
|
||||||
>(null);
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
if (chartRef.value && !chartRef.value.instance) {
|
|
||||||
chartRef.value.instance = new ChartJs(chartRef.value, config);
|
|
||||||
getRef?.(chartRef.value.instance);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<canvas :class="cn(chart, className)" ref="chartRef" v-bind="props" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/chart/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Chart } from "./Chart.vue";
|
|
||||||
export { getColor } from "./utils";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Chart, getColor } from "@/components/ui/chart";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<Chart class="max-w-100" :config="{
|
|
||||||
type: 'bar',
|
|
||||||
data: {
|
|
||||||
labels: [
|
|
||||||
'Jan',
|
|
||||||
'Feb',
|
|
||||||
'Mar',
|
|
||||||
'Apr',
|
|
||||||
'May',
|
|
||||||
'Jun',
|
|
||||||
'Jul',
|
|
||||||
'Aug',
|
|
||||||
'Sep',
|
|
||||||
'Oct',
|
|
||||||
'Nov',
|
|
||||||
'Dec',
|
|
||||||
],
|
|
||||||
datasets: [
|
|
||||||
{
|
|
||||||
label: 'Html Template',
|
|
||||||
maxBarThickness: 12,
|
|
||||||
data: [
|
|
||||||
60, 150, 30, 200, 180, 50, 180, 120, 230, 180, 250, 270,
|
|
||||||
],
|
|
||||||
backgroundColor: () => getColor('--color-foreground', 0.3),
|
|
||||||
borderColor: () => getColor('--color-foreground'),
|
|
||||||
borderWidth: 1,
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
options: {
|
|
||||||
maintainAspectRatio: false,
|
|
||||||
plugins: {
|
|
||||||
legend: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
scales: {
|
|
||||||
x: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
y: {
|
|
||||||
display: false,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,235 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
CheckboxRoot,
|
|
||||||
CheckboxLabel,
|
|
||||||
CheckboxControl,
|
|
||||||
} from "@/components/ui/checkbox";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel>Accept terms and conditions</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel>Accept terms and conditions</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/checkbox</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/checkbox/CheckboxRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { checkboxRoot } from "@mykopkb/core/styles/checkbox.styles";
|
|
||||||
import * as checkbox from "@zag-js/checkbox";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
import type { Props } from "@zag-js/checkbox";
|
|
||||||
import { CheckboxHiddenInput } from ".";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
checked = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string }>();
|
|
||||||
|
|
||||||
const service = useMachine(
|
|
||||||
checkbox.machine,
|
|
||||||
computed(() => ({
|
|
||||||
...props,
|
|
||||||
checked,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
|
|
||||||
const api = computed(() => checkbox.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("checkboxApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<label :class="cn(checkboxRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot />
|
|
||||||
<CheckboxHiddenInput />
|
|
||||||
</label>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/checkbox/CheckboxLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import type { Api } from "@zag-js/checkbox";
|
|
||||||
import { checkboxLabel } from "@mykopkb/core/styles/checkbox.styles";
|
|
||||||
import { label } from "@mykopkb/core/styles/label.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
asChild?: boolean;
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("checkboxApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([label, checkboxLabel, className])" v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/checkbox/CheckboxControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { CheckIcon } from "@lucide/vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { checkboxControl } from "@mykopkb/core/styles/checkbox.styles";
|
|
||||||
import { CheckboxIndicator } from ".";
|
|
||||||
import type { Api } from "@zag-js/checkbox";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
asChild?: boolean;
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("checkboxApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(checkboxControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<CheckboxIndicator>
|
|
||||||
<CheckIcon />
|
|
||||||
</CheckboxIndicator>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/checkbox/CheckboxIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/checkbox";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { checkboxIndicator } from "@mykopkb/core/styles/checkbox.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
asChild?: boolean;
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("checkboxApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(checkboxIndicator, className)" v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/checkbox/CheckboxHiddenInput.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/checkbox";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { checkboxHiddenInput } from "@mykopkb/core/styles/checkbox.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("checkboxApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input :class="cn(checkboxHiddenInput, className)"
|
|
||||||
v-bind="{ ...props, ...$attrs, ...api?.getHiddenInputProps() }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/checkbox/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as CheckboxRoot } from "./CheckboxRoot.vue";
|
|
||||||
export { default as CheckboxLabel } from "./CheckboxLabel.vue";
|
|
||||||
export { default as CheckboxControl } from "./CheckboxControl.vue";
|
|
||||||
export { default as CheckboxIndicator } from "./CheckboxIndicator.vue";
|
|
||||||
export { default as CheckboxHiddenInput } from "./CheckboxHiddenInput.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
CheckboxRoot,
|
|
||||||
CheckboxLabel,
|
|
||||||
CheckboxControl,
|
|
||||||
} from "@/components/ui/checkbox";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel>Accept terms and conditions</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,560 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
DialogRoot,
|
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
DialogDescription,
|
|
||||||
DialogCloseTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import { ref } from "vue";
|
|
||||||
const dialog = ref(false);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<DialogRoot>
|
|
||||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Dialog Title</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DialogCloseTrigger />
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<DialogRoot>
|
|
||||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Dialog Title</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DialogCloseTrigger />
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/dialog</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { provide, computed } from "vue";
|
|
||||||
import * as dialog from "@zag-js/dialog";
|
|
||||||
import type { Props } from "@zag-js/dialog";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
open = undefined,
|
|
||||||
closeOnInteractOutside = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
Partial<Props> & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const service = useMachine(dialog.machine, {
|
|
||||||
...props,
|
|
||||||
get open() {
|
|
||||||
return open;
|
|
||||||
},
|
|
||||||
closeOnInteractOutside,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const api = computed(() => dialog.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("dialogApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogTrigger } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps() }">
|
|
||||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(dialogTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogBackdrop.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogBackdrop } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(dialogBackdrop, className)" v-bind="{ ...props, ...$attrs, ...api?.getBackdropProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogPositioner } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(dialogPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogContent } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { DialogBackdrop, DialogPositioner } from "@/components/ui/dialog";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<DialogBackdrop />
|
|
||||||
<DialogPositioner>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getContentProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<Box raised="double" :class="cn(dialogContent, className)" v-bind="{ ...props }">
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</DialogPositioner>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogTitle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogTitle } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(dialogTitle, className)" v-bind="{ ...props, ...$attrs, ...api?.getTitleProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogDescription } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(dialogDescription, className)" v-bind="{ ...props, ...$attrs, ...api?.getDescriptionProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/DialogCloseTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { dialogCloseTrigger } from "@mykopkb/core/styles/dialog.styles";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import {
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonVariants,
|
|
||||||
} from "@mykopkb/core/styles/button.styles";
|
|
||||||
import { X } from "@lucide/vue";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
look = "outline",
|
|
||||||
variant = "secondary",
|
|
||||||
size,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ButtonVariants & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("dialogApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getCloseTriggerProps() }">
|
|
||||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(dialogCloseTrigger, className)"
|
|
||||||
v-bind="{ ...props }">
|
|
||||||
<X class="size-4" />
|
|
||||||
</Button>
|
|
||||||
<template v-else>
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Button v-else :class="
|
|
||||||
cn(buttonVariants({ look, variant, size, className }), className)
|
|
||||||
">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/dialog/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as DialogRoot } from "./DialogRoot.vue";
|
|
||||||
export { default as DialogTrigger } from "./DialogTrigger.vue";
|
|
||||||
export { default as DialogBackdrop } from "./DialogBackdrop.vue";
|
|
||||||
export { default as DialogPositioner } from "./DialogPositioner.vue";
|
|
||||||
export { default as DialogContent } from "./DialogContent.vue";
|
|
||||||
export { default as DialogTitle } from "./DialogTitle.vue";
|
|
||||||
export { default as DialogDescription } from "./DialogDescription.vue";
|
|
||||||
export { default as DialogCloseTrigger } from "./DialogCloseTrigger.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
DialogRoot,
|
|
||||||
DialogTrigger,
|
|
||||||
DialogContent,
|
|
||||||
DialogTitle,
|
|
||||||
DialogDescription,
|
|
||||||
DialogCloseTrigger,
|
|
||||||
} from "@/components/ui/dialog";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<DialogRoot>
|
|
||||||
<DialogTrigger>Open Dialog</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Dialog Title</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<DialogCloseTrigger />
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<DialogRoot>
|
|
||||||
<DialogTrigger>Custom Close</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Share Link</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<DialogRoot>
|
|
||||||
<DialogTrigger>Custom Close</DialogTrigger>
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Share Link</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Button look="outline" variant="secondary" @click.prevent="dialog = true">
|
|
||||||
Programmatic Trigger
|
|
||||||
</Button>
|
|
||||||
<DialogRoot :open="dialog" @openChange="(details) => (dialog = details.open)">
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Share Link</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
const dialog = ref(false);
|
|
||||||
|
|
||||||
<Button look="outline" variant="secondary" @click.prevent="dialog = true">
|
|
||||||
Programmatic Trigger
|
|
||||||
</Button>
|
|
||||||
<DialogRoot :open="dialog" @openChange="(details) => (dialog = details.open)">
|
|
||||||
<DialogContent>
|
|
||||||
<DialogTitle>Share Link</DialogTitle>
|
|
||||||
<DialogDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</DialogDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<DialogCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</DialogCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</DialogContent>
|
|
||||||
</DialogRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,484 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
CheckboxRoot,
|
|
||||||
CheckboxLabel,
|
|
||||||
CheckboxControl,
|
|
||||||
} from "@/components/ui/checkbox";
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
FieldDescription,
|
|
||||||
FieldGroup,
|
|
||||||
FieldLabel,
|
|
||||||
FieldLegend,
|
|
||||||
FieldSeparator,
|
|
||||||
FieldSet,
|
|
||||||
FieldTitle,
|
|
||||||
FieldContent,
|
|
||||||
} from "@/components/ui/field";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
SelectRoot,
|
|
||||||
SelectControl,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValueText,
|
|
||||||
SelectContent,
|
|
||||||
SelectItemGroup,
|
|
||||||
SelectItemGroupLabel,
|
|
||||||
SelectItem,
|
|
||||||
SelectItemText,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import * as select from "@zag-js/select";
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import {
|
|
||||||
RadioGroupRoot,
|
|
||||||
RadioGroupItem,
|
|
||||||
RadioGroupItemControl,
|
|
||||||
} from "@/components/ui/radio-group";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<div class="w-full max-w-md">
|
|
||||||
<form>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Payment Method</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
All transactions are secure and encrypted
|
|
||||||
</FieldDescription>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
|
||||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
|
||||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
||||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSeparator />
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Billing Address</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
The billing address associated with your payment method
|
|
||||||
</FieldDescription>
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel class="font-normal">
|
|
||||||
Same as shipping address
|
|
||||||
</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
</FieldSet>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<Button look="outline" type="submit">Submit</Button>
|
|
||||||
<Button type="button">Cancel</Button>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<div class="w-full max-w-md">
|
|
||||||
<form>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Payment Method</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
All transactions are secure and encrypted
|
|
||||||
</FieldDescription>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
|
||||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
|
||||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
||||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSeparator />
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Billing Address</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
The billing address associated with your payment method
|
|
||||||
</FieldDescription>
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel class="font-normal">
|
|
||||||
Same as shipping address
|
|
||||||
</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
</FieldSet>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<Button look="outline" type="submit">Submit</Button>
|
|
||||||
<Button type="button">Cancel</Button>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</div>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/field/Field.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
fieldVariants,
|
|
||||||
type FieldVariants,
|
|
||||||
} from "@mykopkb/core/styles/field.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
orientation = "vertical",
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
FieldVariants & {
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div role="group" data-part="field" :data-orientation="orientation"
|
|
||||||
:class="cn(fieldVariants({ orientation }), className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/field/FieldContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { fieldContent } from "@mykopkb/core/styles/field.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div data-part="field-content" :class="cn(fieldContent, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/field/FieldDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { fieldDescription } from "@mykopkb/core/styles/field.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<p data-part="field-description" :class="cn(fieldDescription, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</p>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/field/FieldLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { fieldLabel } from "@mykopkb/core/styles/field.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Label data-part="field-label" :class="cn(fieldLabel, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</Label>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/field/FieldSeparator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { fieldSeparator } from "@mykopkb/core/styles/field.styles";
|
|
||||||
import { Separator } from "@/components/ui/separator";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div data-part="field-separator" :data-content="!!$slots.default" :class="cn(fieldSeparator, className)"
|
|
||||||
v-bind="{ ...props }">
|
|
||||||
<Separator class="absolute inset-0 top-1/2" />
|
|
||||||
<span v-if="$slots.default" data-part="field-separator-content">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/field/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Field } from "./Field.vue";
|
|
||||||
export { default as FieldContent } from "./FieldContent.vue";
|
|
||||||
export { default as FieldDescription } from "./FieldDescription.vue";
|
|
||||||
export { default as FieldError } from "./FieldError.vue";
|
|
||||||
export { default as FieldGroup } from "./FieldGroup.vue";
|
|
||||||
export { default as FieldLabel } from "./FieldLabel.vue";
|
|
||||||
export { default as FieldLegend } from "./FieldLegend.vue";
|
|
||||||
export { default as FieldSeparator } from "./FieldSeparator.vue";
|
|
||||||
export { default as FieldSet } from "./FieldSet.vue";
|
|
||||||
export { default as FieldTitle } from "./FieldTitle.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
Field,
|
|
||||||
FieldContent,
|
|
||||||
FieldDescription,
|
|
||||||
FieldError,
|
|
||||||
FieldGroup,
|
|
||||||
FieldLabel,
|
|
||||||
FieldLegend,
|
|
||||||
FieldSeparator,
|
|
||||||
FieldSet,
|
|
||||||
FieldTitle,
|
|
||||||
} from "@/components/ui/field";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="email">Email</FieldLabel>
|
|
||||||
<Input id="email" placeholder="Enter your email" />
|
|
||||||
<FieldDescription>We'll never share your email.</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<form>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Payment Method</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
All transactions are secure and encrypted
|
|
||||||
</FieldDescription>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
|
||||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
|
||||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
||||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSeparator />
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Billing Address</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
The billing address associated with your payment method
|
|
||||||
</FieldDescription>
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel class="font-normal">
|
|
||||||
Same as shipping address
|
|
||||||
</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="comments">Comments</FieldLabel>
|
|
||||||
<Textarea id="comments" placeholder="Add any additional comments" class="resize-none" />
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<Button look="outline" type="submit">Submit</Button>
|
|
||||||
<Button type="button">Cancel</Button>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<form>
|
|
||||||
<FieldGroup>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Payment Method</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
All transactions are secure and encrypted
|
|
||||||
</FieldDescription>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-name">Name on Card</FieldLabel>
|
|
||||||
<Input id="card-name" placeholder="Evil Rabbit" required />
|
|
||||||
</Field>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="card-number">Card Number</FieldLabel>
|
|
||||||
<Input id="card-number" placeholder="1234 5678 9012 3456" required />
|
|
||||||
<FieldDescription>Enter your 16-digit card number</FieldDescription>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSeparator />
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend>Billing Address</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
The billing address associated with your payment method
|
|
||||||
</FieldDescription>
|
|
||||||
<CheckboxRoot>
|
|
||||||
<CheckboxControl />
|
|
||||||
<CheckboxLabel class="font-normal">
|
|
||||||
Same as shipping address
|
|
||||||
</CheckboxLabel>
|
|
||||||
</CheckboxRoot>
|
|
||||||
</FieldSet>
|
|
||||||
<FieldSet>
|
|
||||||
<FieldGroup>
|
|
||||||
<Field>
|
|
||||||
<FieldLabel for="comments">Comments</FieldLabel>
|
|
||||||
<Textarea id="comments" placeholder="Add any additional comments" class="resize-none" />
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</FieldSet>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<Button look="outline" type="submit">Submit</Button>
|
|
||||||
<Button type="button">Cancel</Button>
|
|
||||||
</Field>
|
|
||||||
</FieldGroup>
|
|
||||||
</form>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<FieldGroup class="w-full max-w-xs">
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend variant="label">Compute Environment</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
Select the compute environment for your cluster.
|
|
||||||
</FieldDescription>
|
|
||||||
<RadioGroupRoot defaultValue="React">
|
|
||||||
<FieldLabel>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldContent>
|
|
||||||
<FieldTitle>Kubernetes</FieldTitle>
|
|
||||||
<FieldDescription>
|
|
||||||
Run GPU workloads on a K8s cluster.
|
|
||||||
</FieldDescription>
|
|
||||||
</FieldContent>
|
|
||||||
<RadioGroupItem value="React">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
</RadioGroupItem>
|
|
||||||
</Field>
|
|
||||||
</FieldLabel>
|
|
||||||
<FieldLabel>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldContent>
|
|
||||||
<FieldTitle>Virtual Machine</FieldTitle>
|
|
||||||
<FieldDescription>
|
|
||||||
Access a cluster to run GPU workloads.
|
|
||||||
</FieldDescription>
|
|
||||||
</FieldContent>
|
|
||||||
<RadioGroupItem value="Solid">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
</RadioGroupItem>
|
|
||||||
</Field>
|
|
||||||
</FieldLabel>
|
|
||||||
</RadioGroupRoot>
|
|
||||||
</FieldSet>
|
|
||||||
</FieldGroup>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<FieldGroup class="w-full max-w-xs">
|
|
||||||
<FieldSet>
|
|
||||||
<FieldLegend variant="label">Compute Environment</FieldLegend>
|
|
||||||
<FieldDescription>
|
|
||||||
Select the compute environment for your cluster.
|
|
||||||
</FieldDescription>
|
|
||||||
<RadioGroupRoot defaultValue="React">
|
|
||||||
<FieldLabel>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldContent>
|
|
||||||
<FieldTitle>Kubernetes</FieldTitle>
|
|
||||||
<FieldDescription>
|
|
||||||
Run GPU workloads on a K8s cluster.
|
|
||||||
</FieldDescription>
|
|
||||||
</FieldContent>
|
|
||||||
<RadioGroupItem value="React">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
</RadioGroupItem>
|
|
||||||
</Field>
|
|
||||||
</FieldLabel>
|
|
||||||
<FieldLabel>
|
|
||||||
<Field orientation="horizontal">
|
|
||||||
<FieldContent>
|
|
||||||
<FieldTitle>Virtual Machine</FieldTitle>
|
|
||||||
<FieldDescription>
|
|
||||||
Access a cluster to run GPU workloads.
|
|
||||||
</FieldDescription>
|
|
||||||
</FieldContent>
|
|
||||||
<RadioGroupItem value="Solid">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
</RadioGroupItem>
|
|
||||||
</Field>
|
|
||||||
</FieldLabel>
|
|
||||||
</RadioGroupRoot>
|
|
||||||
</FieldSet>
|
|
||||||
</FieldGroup>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,74 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Input class="w-84" type="email" placeholder="Email" />
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Input class="w-84" type="email" placeholder="Email" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/input/Input.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { input } from "@mykopkb/core/styles/input.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
type,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
type?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input :type="type" :class="cn(input, className)" v-bind="{ ...props }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/input/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Input } from "./Input.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Input class="w-84" type="email" placeholder="Email" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,369 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Map } from "@/components/ui/map";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
const markersData = {
|
|
||||||
type: "FeatureCollection" as const,
|
|
||||||
features: [
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Cafe Berlin", type: "cafe" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.388, 52.517],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Restaurant Alex", type: "restaurant" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.39, 52.518],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Coffee House", type: "cafe" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.385, 52.515],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Pizza Place", type: "restaurant" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.392, 52.519],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Burger Joint", type: "restaurant" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.387, 52.516],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Starbucks", type: "cafe" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.391, 52.52],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Sushi Bar", type: "restaurant" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.395, 52.522],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Bakery", type: "cafe" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.383, 52.514],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Italian Restaurant", type: "restaurant" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.398, 52.525],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
{
|
|
||||||
type: "Feature" as const,
|
|
||||||
properties: { name: "Tea House", type: "cafe" },
|
|
||||||
geometry: {
|
|
||||||
type: "Point" as const,
|
|
||||||
coordinates: [13.38, 52.512],
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
};
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Map class="w-full h-96" :center="[13.388, 52.517]" :zoom="9.5" :markers="markersData" />
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Map class="w-full h-96" :center="[13.388, 52.517]" :zoom="9.5" :markers="markersData" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add maplibre-gl</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/map/Map.vue">
|
|
||||||
{{`
|
|
||||||
<${""}script setup lang="ts">
|
|
||||||
import "maplibre-gl/dist/maplibre-gl.css";
|
|
||||||
import maplibregl, { type MapOptions } from "maplibre-gl";
|
|
||||||
import { ref, onMounted } from "vue";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Plus, Minus, MapPin, Compass, Expand, X } from "@lucide/vue";
|
|
||||||
import type { FeatureCollection, Point } from "geojson";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { map } from "@mykopkb/core/styles/map.styles";
|
|
||||||
|
|
||||||
const mapRef = ref();
|
|
||||||
const mapInstance = ref<maplibregl.Map>();
|
|
||||||
const isFullscreen = ref(false);
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
markers,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
Partial<MapOptions> & {
|
|
||||||
class?: string;
|
|
||||||
markers?: FeatureCollection<Point>;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
onMounted(() => {
|
|
||||||
mapInstance.value = new maplibregl.Map({
|
|
||||||
...Object.fromEntries(Object.entries(props).filter(([_, value]) => value)),
|
|
||||||
style: "https://tiles.openfreemap.org/styles/positron",
|
|
||||||
container: mapRef.value,
|
|
||||||
});
|
|
||||||
|
|
||||||
markers &&
|
|
||||||
mapInstance.value.on("load", () => {
|
|
||||||
// Add source with clustering
|
|
||||||
mapInstance.value!.addSource("markers", {
|
|
||||||
type: "geojson",
|
|
||||||
data: markers,
|
|
||||||
cluster: true,
|
|
||||||
clusterMaxZoom: 14, // Max zoom for clustering
|
|
||||||
clusterRadius: 50, // Cluster radius in pixels
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layer for clusters (circles)
|
|
||||||
mapInstance.value!.addLayer({
|
|
||||||
id: "clusters",
|
|
||||||
type: "circle",
|
|
||||||
source: "markers",
|
|
||||||
filter: ["has", "point_count"],
|
|
||||||
paint: {
|
|
||||||
"circle-color": [
|
|
||||||
"step",
|
|
||||||
["get", "point_count"],
|
|
||||||
"#cccccc", // Color for < 5 points
|
|
||||||
5,
|
|
||||||
"#cccccc", // Color for 5-10 points
|
|
||||||
10,
|
|
||||||
"#cccccc", // Color for > 10 points
|
|
||||||
],
|
|
||||||
"circle-radius": [
|
|
||||||
"step",
|
|
||||||
["get", "point_count"],
|
|
||||||
20, // Radius for < 5 points
|
|
||||||
5,
|
|
||||||
30, // Radius for 5-10 points
|
|
||||||
10,
|
|
||||||
40, // Radius for > 10 points
|
|
||||||
],
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layer for cluster count numbers
|
|
||||||
mapInstance.value!.addLayer({
|
|
||||||
id: "cluster-count",
|
|
||||||
type: "symbol",
|
|
||||||
source: "markers",
|
|
||||||
filter: ["has", "point_count"],
|
|
||||||
layout: {
|
|
||||||
"text-field": "{point_count_abbreviated}",
|
|
||||||
"text-font": ["Open Sans Bold"],
|
|
||||||
"text-size": 12,
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Layer for individual points (unclustered)
|
|
||||||
mapInstance.value!.addLayer({
|
|
||||||
id: "unclustered-point",
|
|
||||||
type: "circle",
|
|
||||||
source: "markers",
|
|
||||||
filter: ["!", ["has", "point_count"]],
|
|
||||||
paint: {
|
|
||||||
"circle-color": "#333333",
|
|
||||||
"circle-radius": 8,
|
|
||||||
"circle-stroke-width": 2,
|
|
||||||
"circle-stroke-color": "#ffffff",
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click on cluster to zoom in
|
|
||||||
mapInstance.value!.on("click", "clusters", async (e) => {
|
|
||||||
const features = mapInstance.value!.queryRenderedFeatures(e.point, {
|
|
||||||
layers: ["clusters"],
|
|
||||||
});
|
|
||||||
|
|
||||||
const clusterId = features[0]?.properties.cluster_id;
|
|
||||||
const source = mapInstance.value!.getSource(
|
|
||||||
"markers"
|
|
||||||
) as maplibregl.GeoJSONSource;
|
|
||||||
|
|
||||||
try {
|
|
||||||
const zoom = await source.getClusterExpansionZoom(clusterId);
|
|
||||||
|
|
||||||
mapInstance.value!.easeTo({
|
|
||||||
center: (features[0]?.geometry as any).coordinates,
|
|
||||||
zoom: zoom,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
|
||||||
console.error("Error getting cluster expansion zoom:", err);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
// Click on individual point to show popup
|
|
||||||
mapInstance.value!.on("click", "unclustered-point", (e) => {
|
|
||||||
const coordinates = (
|
|
||||||
e.features![0]?.geometry as any
|
|
||||||
).coordinates.slice();
|
|
||||||
const { name, type } = (e.features![0]?.properties || {}) as any;
|
|
||||||
|
|
||||||
new maplibregl.Popup()
|
|
||||||
.setLngLat(coordinates)
|
|
||||||
.setHTML(\`<h3>\${name}</h3><p>\${type}</p>\`)
|
|
||||||
.addTo(mapInstance.value!);
|
|
||||||
});
|
|
||||||
|
|
||||||
// Change cursor on hover cluster/point
|
|
||||||
mapInstance.value!.on("mouseenter", "clusters", () => {
|
|
||||||
mapInstance.value!.getCanvas().style.cursor = "pointer";
|
|
||||||
});
|
|
||||||
mapInstance.value!.on("mouseleave", "clusters", () => {
|
|
||||||
mapInstance.value!.getCanvas().style.cursor = "";
|
|
||||||
});
|
|
||||||
mapInstance.value!.on("mouseenter", "unclustered-point", () => {
|
|
||||||
mapInstance.value!.getCanvas().style.cursor = "pointer";
|
|
||||||
});
|
|
||||||
mapInstance.value!.on("mouseleave", "unclustered-point", () => {
|
|
||||||
mapInstance.value!.getCanvas().style.cursor = "";
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const zoomIn = () => {
|
|
||||||
mapInstance.value?.zoomIn();
|
|
||||||
};
|
|
||||||
|
|
||||||
const zoomOut = () => {
|
|
||||||
mapInstance.value?.zoomOut();
|
|
||||||
};
|
|
||||||
|
|
||||||
const resetNorth = () => {
|
|
||||||
mapInstance.value?.easeTo({ bearing: 0, pitch: 0 });
|
|
||||||
};
|
|
||||||
|
|
||||||
const locateMe = () => {
|
|
||||||
if (!navigator.geolocation) {
|
|
||||||
alert("Geolocation is not supported by your browser");
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
navigator.geolocation.getCurrentPosition(
|
|
||||||
(position) => {
|
|
||||||
const { longitude, latitude } = position.coords;
|
|
||||||
mapInstance.value?.flyTo({
|
|
||||||
center: [longitude, latitude],
|
|
||||||
zoom: 14,
|
|
||||||
});
|
|
||||||
|
|
||||||
new maplibregl.Marker({ color: "var(--color-foreground)" })
|
|
||||||
.setLngLat([longitude, latitude])
|
|
||||||
.addTo(mapInstance.value!);
|
|
||||||
},
|
|
||||||
(error) => {
|
|
||||||
alert("Failed to get location: " + error.message);
|
|
||||||
}
|
|
||||||
);
|
|
||||||
};
|
|
||||||
|
|
||||||
const toggleFullscreen = () => {
|
|
||||||
if (!mapRef.value) return;
|
|
||||||
|
|
||||||
if (!isFullscreen.value) {
|
|
||||||
if (mapRef.value.requestFullscreen) {
|
|
||||||
mapRef.value.requestFullscreen();
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
if (document.exitFullscreen) {
|
|
||||||
document.exitFullscreen();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
isFullscreen.value = !isFullscreen.value;
|
|
||||||
};
|
|
||||||
</${""}script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div data-scope="map" data-part="root" ref="mapRef" :class="cn(map, className)">
|
|
||||||
<div data-scope="map" data-part="controls">
|
|
||||||
<Button data-scope="map" data-part="zoom-in" @click="zoomIn" variant="ghost" size="sm">
|
|
||||||
<Plus />
|
|
||||||
</Button>
|
|
||||||
<Button data-scope="map" data-part="zoom-out" @click="zoomOut" variant="ghost" size="sm">
|
|
||||||
<Minus />
|
|
||||||
</Button>
|
|
||||||
<Button data-scope="map" data-part="reset-north" @click="resetNorth" variant="ghost" size="sm">
|
|
||||||
<Compass />
|
|
||||||
</Button>
|
|
||||||
<Button data-scope="map" data-part="locate" @click="locateMe" variant="ghost" size="sm">
|
|
||||||
<MapPin />
|
|
||||||
</Button>
|
|
||||||
<Button data-scope="map" data-part="toggle-fullscreen" @click="toggleFullscreen" variant="ghost" size="sm">
|
|
||||||
<Expand v-if="!isFullscreen" />
|
|
||||||
<X v-else />
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/map/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Map } from "./Map.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Map } from "@/components/ui/map";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Map class="w-full h-96" :center="[13.388, 52.517]" :zoom="9.5" :markers="markersData" />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,831 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
MenuRoot,
|
|
||||||
MenuTrigger,
|
|
||||||
MenuPositioner,
|
|
||||||
MenuContent,
|
|
||||||
MenuItem,
|
|
||||||
MenuCheckboxItem,
|
|
||||||
MenuSeparator,
|
|
||||||
MenuTriggerItem,
|
|
||||||
MenuRadioItemGroup,
|
|
||||||
MenuItemGroupLabel,
|
|
||||||
MenuRadioItem,
|
|
||||||
} from "@/components/ui/menu";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import { ref } from "vue";
|
|
||||||
const react = ref(false);
|
|
||||||
const solid = ref(false);
|
|
||||||
const vue = ref(false);
|
|
||||||
const svelte = ref(false);
|
|
||||||
const value = ref("react");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/menu</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as menu from "@zag-js/menu";
|
|
||||||
import type { Props } from "@zag-js/menu";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { menuRoot } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
closeOnSelect = false,
|
|
||||||
open = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(menu.machine, {
|
|
||||||
...props,
|
|
||||||
open,
|
|
||||||
closeOnSelect,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => menu.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("menuApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuRoot, className)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { menuTrigger } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { MenuIndicator } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<Button variant="ghost" v-if="!asChild" :class="cn(menuTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
<MenuIndicator />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { ChevronDown } from "@lucide/vue";
|
|
||||||
import { menuIndicator } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/menu";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<ChevronDown v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { menuPositioner } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<Slot :class="cn(menuPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { menuContent } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Box v-else raised="single" :class="cn(menuContent, className)">
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { type Api, type ItemProps } from "@zag-js/menu";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
shortcut,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ItemProps & {
|
|
||||||
class?: string;
|
|
||||||
shortcut?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<div>\{\{ shortcut \}\}</div>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuTriggerItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/menu";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { ChevronRight } from "@lucide/vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuItem, className)" v-bind="{ ...api?.getTriggerItemProps(api), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<ChevronRight data-part="nested-menu-chevron" />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuCheckboxItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api, OptionItemProps } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Check } from "@lucide/vue";
|
|
||||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
shortcut,
|
|
||||||
class: className,
|
|
||||||
type = "checkbox",
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
Omit<OptionItemProps, "type"> & {
|
|
||||||
class?: string;
|
|
||||||
shortcut?: string;
|
|
||||||
type?: OptionItemProps["type"];
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(menuItem, className)" v-bind="{
|
|
||||||
...props,
|
|
||||||
...$attrs,
|
|
||||||
...api?.getOptionItemProps({
|
|
||||||
...props,
|
|
||||||
type,
|
|
||||||
}),
|
|
||||||
}">
|
|
||||||
<div>
|
|
||||||
<span data-part="item-indicator" v-bind="{ ...api?.getItemIndicatorProps(props) }">
|
|
||||||
<Check />
|
|
||||||
</span>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<div>\{\{ shortcut \}\}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuRadioItemGroup.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { menuRadioItemGroup } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuRadioItemGroup, className)" v-bind="{
|
|
||||||
...props,
|
|
||||||
...$attrs,
|
|
||||||
}">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuItemGroupLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { menuItemGroupLabel } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuItemGroupLabel, className)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<label v-else>
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuRadioItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api, OptionItemProps } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { Dot } from "@lucide/vue";
|
|
||||||
import { menuItem } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
shortcut,
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
type = "radio",
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
Omit<OptionItemProps, "type"> & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
shortcut?: string;
|
|
||||||
type?: OptionItemProps["type"];
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(menuItem, className)" v-bind="{
|
|
||||||
...props,
|
|
||||||
...$attrs,
|
|
||||||
...api?.getOptionItemProps({
|
|
||||||
...props,
|
|
||||||
type,
|
|
||||||
}),
|
|
||||||
}">
|
|
||||||
<div>
|
|
||||||
<span data-part="item-indicator" v-bind="{ ...api?.getItemIndicatorProps(props) }">
|
|
||||||
<Dot />
|
|
||||||
</span>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<div>\{\{ shortcut \}\}</div>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/MenuSeparator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/menu";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { menuSeparator } from "@mykopkb/core/styles/menu.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("menuApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(menuSeparator, className)" v-bind="{
|
|
||||||
...props,
|
|
||||||
...$attrs,
|
|
||||||
...api?.getSeparatorProps(),
|
|
||||||
}">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<hr v-else>
|
|
||||||
<slot />
|
|
||||||
</hr>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/menu/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as MenuRoot } from "./MenuRoot.vue";
|
|
||||||
export { default as MenuTrigger } from "./MenuTrigger.vue";
|
|
||||||
export { default as MenuIndicator } from "./MenuIndicator.vue";
|
|
||||||
export { default as MenuPositioner } from "./MenuPositioner.vue";
|
|
||||||
export { default as MenuContent } from "./MenuContent.vue";
|
|
||||||
export { default as MenuItem } from "./MenuItem.vue";
|
|
||||||
export { default as MenuTriggerItem } from "./MenuTriggerItem.vue";
|
|
||||||
export { default as MenuCheckboxItem } from "./MenuCheckboxItem.vue";
|
|
||||||
export { default as MenuRadioItemGroup } from "./MenuRadioItemGroup.vue";
|
|
||||||
export { default as MenuItemGroupLabel } from "./MenuItemGroupLabel.vue";
|
|
||||||
export { default as MenuRadioItem } from "./MenuRadioItem.vue";
|
|
||||||
export { default as MenuSeparator } from "./MenuSeparator.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
MenuRoot,
|
|
||||||
MenuTrigger,
|
|
||||||
MenuPositioner,
|
|
||||||
MenuContent,
|
|
||||||
MenuItem,
|
|
||||||
MenuCheckboxItem,
|
|
||||||
MenuSeparator,
|
|
||||||
MenuTriggerItem,
|
|
||||||
MenuRadioItemGroup,
|
|
||||||
MenuItemGroupLabel,
|
|
||||||
MenuRadioItem,
|
|
||||||
} from "@/components/ui/menu";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem shortcut="⇧⌘P" value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘B" value="solid">
|
|
||||||
Solid
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘S" value="vue">
|
|
||||||
Vue
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘K" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem shortcut="⇧⌘P" value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘B" value="solid">
|
|
||||||
Solid
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘S" value="vue">
|
|
||||||
Vue
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘K" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem shortcut="⇧⌘P" value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘B" value="solid">
|
|
||||||
Solid
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘S" value="vue">
|
|
||||||
Vue
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘K" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
<MenuRoot :positioning="{ placement: 'right-start', gutter: 12 }">
|
|
||||||
<MenuTriggerItem>Frameworks</MenuTriggerItem>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem disabled value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem shortcut="⇧⌘P" value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘B" value="solid">
|
|
||||||
Solid
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘S" value="vue">
|
|
||||||
Vue
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem shortcut="⌘K" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
<MenuRoot :positioning="{ placement: 'right-start', gutter: 12 }">
|
|
||||||
<MenuTriggerItem>Frameworks</MenuTriggerItem>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuItem value="react">React</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem value="svelte">Svelte</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
<MenuSeparator />
|
|
||||||
<MenuItem disabled value="react">
|
|
||||||
React
|
|
||||||
</MenuItem>
|
|
||||||
<MenuItem value="solid">Solid</MenuItem>
|
|
||||||
<MenuItem value="vue">Vue</MenuItem>
|
|
||||||
<MenuItem shortcut="⇧⌘Q" value="svelte">
|
|
||||||
Svelte
|
|
||||||
</MenuItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuCheckboxItem :checked="react" :onCheckedChange="(checked) => (react = checked)" value="checked">
|
|
||||||
React
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="solid" :onCheckedChange="(checked) => (solid = checked)" value="checked">
|
|
||||||
Solid
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="vue" :onCheckedChange="(checked) => (vue = checked)" value="checked">
|
|
||||||
Vue
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="svelte" :onCheckedChange="(checked) => (svelte = checked)" value="checked">
|
|
||||||
Svelte
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuCheckboxItem :checked="react" :onCheckedChange="(checked) => (react = checked)" value="checked">
|
|
||||||
React
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="solid" :onCheckedChange="(checked) => (solid = checked)" value="checked">
|
|
||||||
Solid
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="vue" :onCheckedChange="(checked) => (vue = checked)" value="checked">
|
|
||||||
Vue
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
<MenuCheckboxItem :checked="svelte" :onCheckedChange="(checked) => (svelte = checked)" value="checked">
|
|
||||||
Svelte
|
|
||||||
</MenuCheckboxItem>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuRadioItemGroup>
|
|
||||||
<MenuItemGroupLabel>Frameworks</MenuItemGroupLabel>
|
|
||||||
<MenuRadioItem v-for="framework in ['React', 'Solid', 'Vue', 'Svelte']" :key="framework"
|
|
||||||
:value="framework" :checked="framework == value" :onCheckedChange="(checked) => (checked ? (value = framework) : '')
|
|
||||||
">
|
|
||||||
{{ framework }}
|
|
||||||
</MenuRadioItem>
|
|
||||||
</MenuRadioItemGroup>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<MenuRoot class="w-56">
|
|
||||||
<MenuTrigger>Open menu</MenuTrigger>
|
|
||||||
<MenuPositioner>
|
|
||||||
<MenuContent>
|
|
||||||
<MenuRadioItemGroup>
|
|
||||||
<MenuItemGroupLabel>Frameworks</MenuItemGroupLabel>
|
|
||||||
<MenuRadioItem v-for="framework in ['React', 'Solid', 'Vue', 'Svelte']" :key="framework"
|
|
||||||
:value="framework" :checked="framework == value" :onCheckedChange="
|
|
||||||
(checked) => (checked ? (value = framework) : '')
|
|
||||||
">
|
|
||||||
\{\{ framework \}\}
|
|
||||||
</MenuRadioItem>
|
|
||||||
</MenuRadioItemGroup>
|
|
||||||
</MenuContent>
|
|
||||||
</MenuPositioner>
|
|
||||||
</MenuRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,298 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
PaginationContext,
|
|
||||||
PaginationRoot,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationPrevTrigger,
|
|
||||||
PaginationNextTrigger,
|
|
||||||
PaginationEllipsis,
|
|
||||||
} from "@/components/ui/pagination";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
|
||||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
|
||||||
<PaginationContext v-slot="{ pagination }">
|
|
||||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
|
||||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
|
||||||
{{ page.value }}
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationEllipsis v-else :index="index" />
|
|
||||||
</template>
|
|
||||||
</PaginationContext>
|
|
||||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
|
||||||
</PaginationRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
|
||||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
|
||||||
<PaginationContext v-slot="{ pagination }">
|
|
||||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
|
||||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
|
||||||
\{\{ page.value \}\}
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationEllipsis v-else :index="index" />
|
|
||||||
</template>
|
|
||||||
</PaginationContext>
|
|
||||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
|
||||||
</PaginationRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/pagination</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationContext.vue">
|
|
||||||
{{ `
|
|
||||||
<script setup lang="ts">
|
|
||||||
import type { Api } from "@zag-js/pagination";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const api = inject<Api>("paginationApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot :pagination="api"></slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationEllipsis.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { paginationEllipsis } from "@mykopkb/core/styles/pagination.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, EllipsisProps } from "@zag-js/pagination";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
EllipsisProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("paginationApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(paginationEllipsis, className)"
|
|
||||||
v-bind="{ ...api?.getEllipsisProps(props), ...props, ...$attrs }">
|
|
||||||
<div v-if="!$slots.default">…</div>
|
|
||||||
<template v-else>
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { paginationItem } from "@mykopkb/core/styles/pagination.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/pagination";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ItemProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("paginationApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(paginationItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationNextTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { paginationNextTrigger } from "@mykopkb/core/styles/pagination.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/pagination";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("paginationApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(paginationNextTrigger, className)"
|
|
||||||
v-bind="{ ...api?.getNextTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationPrevTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { paginationPrevTrigger } from "@mykopkb/core/styles/pagination.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/pagination";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("paginationApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(paginationPrevTrigger, className)"
|
|
||||||
v-bind="{ ...api?.getPrevTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/PaginationRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as pagination from "@zag-js/pagination";
|
|
||||||
import type { Props } from "@zag-js/pagination";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { paginationRoot } from "@mykopkb/core/styles/pagination.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
count,
|
|
||||||
pageSize,
|
|
||||||
siblingCount,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(pagination.machine, {
|
|
||||||
...props,
|
|
||||||
count,
|
|
||||||
pageSize,
|
|
||||||
siblingCount,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => pagination.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("paginationApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(paginationRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/pagination/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as PaginationContext } from "./PaginationContext.vue";
|
|
||||||
export { default as PaginationEllipsis } from "./PaginationEllipsis.vue";
|
|
||||||
export { default as PaginationItem } from "./PaginationItem.vue";
|
|
||||||
export { default as PaginationNextTrigger } from "./PaginationNextTrigger.vue";
|
|
||||||
export { default as PaginationPrevTrigger } from "./PaginationPrevTrigger.vue";
|
|
||||||
export { default as PaginationRoot } from "./PaginationRoot.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
PaginationContext,
|
|
||||||
PaginationRoot,
|
|
||||||
PaginationItem,
|
|
||||||
PaginationPrevTrigger,
|
|
||||||
PaginationNextTrigger,
|
|
||||||
PaginationEllipsis,
|
|
||||||
} from "@/components/ui/pagination";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<PaginationRoot :count="5000" :pageSize="10" :siblingCount="2">
|
|
||||||
<PaginationPrevTrigger>Previous</PaginationPrevTrigger>
|
|
||||||
<PaginationContext v-slot="{ pagination }">
|
|
||||||
<template v-for="(page, index) in pagination?.pages" :key="index">
|
|
||||||
<PaginationItem v-if="page.type === 'page'" v-bind="{ ...page }">
|
|
||||||
\{\{ page.value \}\}
|
|
||||||
</PaginationItem>
|
|
||||||
<PaginationEllipsis v-else :index="index" />
|
|
||||||
</template>
|
|
||||||
</PaginationContext>
|
|
||||||
<PaginationNextTrigger>Next</PaginationNextTrigger>
|
|
||||||
</PaginationRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,447 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
PopoverRoot,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverPositioner,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTitle,
|
|
||||||
PopoverDescription,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<PopoverRoot>
|
|
||||||
<PopoverTrigger class="w-56">Open Popover</PopoverTrigger>
|
|
||||||
<PopoverPositioner>
|
|
||||||
<PopoverContent class="w-100">
|
|
||||||
<PopoverTitle>Dimensions</PopoverTitle>
|
|
||||||
<PopoverDescription>
|
|
||||||
Set the dimensions for the layer.
|
|
||||||
</PopoverDescription>
|
|
||||||
<div class="grid gap-3 mt-4 mb-2">
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="width">Width</Label>
|
|
||||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxWidth">Max. width</Label>
|
|
||||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="height">Height</Label>
|
|
||||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxHeight">Max. height</Label>
|
|
||||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</PopoverPositioner>
|
|
||||||
</PopoverRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<PopoverRoot>
|
|
||||||
<PopoverTrigger class="w-56">Open Popover</PopoverTrigger>
|
|
||||||
<PopoverPositioner>
|
|
||||||
<PopoverContent class="w-100">
|
|
||||||
<PopoverTitle>Dimensions</PopoverTitle>
|
|
||||||
<PopoverDescription>
|
|
||||||
Set the dimensions for the layer.
|
|
||||||
</PopoverDescription>
|
|
||||||
<div class="grid gap-3 mt-4 mb-2">
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="width">Width</Label>
|
|
||||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxWidth">Max. width</Label>
|
|
||||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="height">Height</Label>
|
|
||||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxHeight">Max. height</Label>
|
|
||||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</PopoverPositioner>
|
|
||||||
</PopoverRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/popover</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverArrow.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { popoverArrow } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(popoverArrow, className)" v-bind="{ ...api?.getArrowProps(), ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverArrowTip.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { popoverArrowTip } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(popoverArrowTip, className)" v-bind="{ ...api?.getArrowTipProps(), ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { popoverContent } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
import { PopoverArrow, PopoverArrowTip } from ".";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(popoverContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Box v-else raised="single" :class="cn(popoverContent, className)">
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
<PopoverArrow>
|
|
||||||
<PopoverArrowTip />
|
|
||||||
</PopoverArrow>
|
|
||||||
</Box>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { popoverDescription } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(popoverDescription, className)"
|
|
||||||
v-bind="{ ...api?.getDescriptionProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { popoverIndicator } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
import { ChevronDown } from "@lucide/vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(popoverIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<ChevronDown v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { popoverPositioner } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<Slot :class="cn(popoverPositioner, className)"
|
|
||||||
v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as popover from "@zag-js/popover";
|
|
||||||
import type { Props } from "@zag-js/popover";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { popoverRoot } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
open = undefined,
|
|
||||||
closeOnInteractOutside = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(popover.machine, {
|
|
||||||
...props,
|
|
||||||
open,
|
|
||||||
closeOnInteractOutside,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => popover.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("popoverApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(popoverRoot, className)" v-bind="{ ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverTitle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { popoverTitle } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(popoverTitle, className)" v-bind="{ ...api?.getTitleProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/PopoverTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { type Api } from "@zag-js/popover";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { popoverTrigger } from "@mykopkb/core/styles/popover.styles";
|
|
||||||
import { PopoverIndicator } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("popoverApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<Button variant="ghost" v-if="!asChild" :class="cn(popoverTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
<PopoverIndicator />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/popover/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as PopoverArrow } from "./PopoverArrow.vue";
|
|
||||||
export { default as PopoverArrowTip } from "./PopoverArrowTip.vue";
|
|
||||||
export { default as PopoverContent } from "./PopoverContent.vue";
|
|
||||||
export { default as PopoverDescription } from "./PopoverDescription.vue";
|
|
||||||
export { default as PopoverIndicator } from "./PopoverIndicator.vue";
|
|
||||||
export { default as PopoverPositioner } from "./PopoverPositioner.vue";
|
|
||||||
export { default as PopoverRoot } from "./PopoverRoot.vue";
|
|
||||||
export { default as PopoverTitle } from "./PopoverTitle.vue";
|
|
||||||
export { default as PopoverTrigger } from "./PopoverTrigger.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
PopoverRoot,
|
|
||||||
PopoverTrigger,
|
|
||||||
PopoverPositioner,
|
|
||||||
PopoverContent,
|
|
||||||
PopoverTitle,
|
|
||||||
PopoverDescription,
|
|
||||||
} from "@/components/ui/popover";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<PopoverRoot>
|
|
||||||
<PopoverTrigger class="w-56"> Open Popover </PopoverTrigger>
|
|
||||||
<PopoverPositioner>
|
|
||||||
<PopoverContent class="w-100">
|
|
||||||
<PopoverTitle>Dimensions</PopoverTitle>
|
|
||||||
<PopoverDescription>
|
|
||||||
Set the dimensions for the layer.
|
|
||||||
</PopoverDescription>
|
|
||||||
<div class="grid gap-3 mt-4 mb-2">
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="width">Width</Label>
|
|
||||||
<Input id="width" defaultValue="100%" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxWidth">Max. width</Label>
|
|
||||||
<Input id="maxWidth" defaultValue="300px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="height">Height</Label>
|
|
||||||
<Input id="height" defaultValue="25px" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
<div class="grid grid-cols-3 items-center gap-4">
|
|
||||||
<Label for="maxHeight">Max. height</Label>
|
|
||||||
<Input id="maxHeight" defaultValue="none" class="col-span-2" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
</PopoverContent>
|
|
||||||
</PopoverPositioner>
|
|
||||||
</PopoverRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,255 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
ProgressRoot,
|
|
||||||
ProgressLabel,
|
|
||||||
ProgressValueText,
|
|
||||||
ProgressCircle,
|
|
||||||
ProgressCircleTrack,
|
|
||||||
ProgressCircleRange,
|
|
||||||
} from "@/components/ui/progress-circular";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
|
||||||
<ProgressCircle class="max-w-48">
|
|
||||||
<ProgressCircleTrack />
|
|
||||||
<ProgressCircleRange />
|
|
||||||
</ProgressCircle>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
|
||||||
<ProgressCircle class="max-w-48">
|
|
||||||
<ProgressCircleTrack />
|
|
||||||
<ProgressCircleRange />
|
|
||||||
</ProgressCircle>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/progress</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressCircle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressCircle } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<svg :class="cn(progressCircle, className)" v-bind="{ ...api?.getCircleProps(), ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</svg>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressCircleRange.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressCircleRange } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<circle :class="cn(progressCircleRange, className)"
|
|
||||||
v-bind="{ ...api?.getCircleRangeProps(), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressCircleTrack.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressCircleTrack } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<circle :class="cn(progressCircleTrack, className)"
|
|
||||||
v-bind="{ ...api?.getCircleTrackProps(), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressLabel } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
|
||||||
<Label v-if="!asChild" :class="cn(progressLabel, className)">
|
|
||||||
<slot />
|
|
||||||
</Label>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as progress from "@zag-js/progress";
|
|
||||||
import type { Props } from "@zag-js/progress";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { progressRoot } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(progress.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => progress.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("progressApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(progressRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/ProgressValueText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressValueText } from "@mykopkb/core/styles/progress-circular.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(progressValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
|
||||||
\{\{ api?.valueAsString \}\}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-circular/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as ProgressCircle } from "./ProgressCircle.vue";
|
|
||||||
export { default as ProgressCircleRange } from "./ProgressCircleRange.vue";
|
|
||||||
export { default as ProgressCircleTrack } from "./ProgressCircleTrack.vue";
|
|
||||||
export { default as ProgressLabel } from "./ProgressLabel.vue";
|
|
||||||
export { default as ProgressRoot } from "./ProgressRoot.vue";
|
|
||||||
export { default as ProgressValueText } from "./ProgressValueText.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
ProgressRoot,
|
|
||||||
ProgressLabel,
|
|
||||||
ProgressValueText,
|
|
||||||
ProgressCircle,
|
|
||||||
ProgressCircleTrack,
|
|
||||||
ProgressCircleRange,
|
|
||||||
} from "@/components/ui/progress-circular";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Circular</ProgressLabel>
|
|
||||||
<ProgressCircle class="max-w-48">
|
|
||||||
<ProgressCircleTrack />
|
|
||||||
<ProgressCircleRange />
|
|
||||||
</ProgressCircle>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,227 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
ProgressRoot,
|
|
||||||
ProgressLabel,
|
|
||||||
ProgressValueText,
|
|
||||||
ProgressTrack,
|
|
||||||
ProgressRange,
|
|
||||||
} from "@/components/ui/progress-linear";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
|
||||||
<ProgressTrack class="max-w-72">
|
|
||||||
<ProgressRange />
|
|
||||||
</ProgressTrack>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
|
||||||
<ProgressTrack class="max-w-72">
|
|
||||||
<ProgressRange />
|
|
||||||
</ProgressTrack>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/progress</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/ProgressLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressLabel } from "@mykopkb/core/styles/progress-linear.styles";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
|
||||||
<Label v-if="!asChild" :class="cn(progressLabel, className)">
|
|
||||||
<slot />
|
|
||||||
</Label>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/ProgressRange.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressRange } from "@mykopkb/core/styles/progress-linear.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(progressRange, className)" v-bind="{ ...api?.getRangeProps(), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/ProgressRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as progress from "@zag-js/progress";
|
|
||||||
import type { Props } from "@zag-js/progress";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { progressRoot } from "@mykopkb/core/styles/progress-linear.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(progress.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => progress.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("progressApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(progressRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/ProgressTrack.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressTrack } from "@mykopkb/core/styles/progress-linear.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(progressTrack, className)" v-bind="{ ...api?.getTrackProps(), ...props, ...$attrs }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/ProgressValueText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { progressValueText } from "@mykopkb/core/styles/progress-linear.styles";
|
|
||||||
import type { Api } from "@zag-js/progress";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("progressApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(progressValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
|
||||||
\{\{ api?.valueAsString \}\}
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/progress-linear/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as ProgressLabel } from "./ProgressLabel.vue";
|
|
||||||
export { default as ProgressRange } from "./ProgressRange.vue";
|
|
||||||
export { default as ProgressRoot } from "./ProgressRoot.vue";
|
|
||||||
export { default as ProgressTrack } from "./ProgressTrack.vue";
|
|
||||||
export { default as ProgressValueText } from "./ProgressValueText.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
ProgressRoot,
|
|
||||||
ProgressLabel,
|
|
||||||
ProgressValueText,
|
|
||||||
ProgressTrack,
|
|
||||||
ProgressRange,
|
|
||||||
} from "@/components/ui/progress-linear";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<ProgressRoot :defaultValue="42">
|
|
||||||
<ProgressLabel>Progress Linear</ProgressLabel>
|
|
||||||
<ProgressTrack class="max-w-72">
|
|
||||||
<ProgressRange />
|
|
||||||
</ProgressTrack>
|
|
||||||
<ProgressValueText />
|
|
||||||
</ProgressRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,325 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
RadioGroupRoot,
|
|
||||||
RadioGroupLabel,
|
|
||||||
RadioGroupItem,
|
|
||||||
RadioGroupItemText,
|
|
||||||
RadioGroupItemControl,
|
|
||||||
} from "@/components/ui/radio-group";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
const frameworks = ["React", "Solid", "Vue", "Svelte"];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<RadioGroupRoot defaultValue="React">
|
|
||||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
|
||||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
<RadioGroupItemText>{{ framework }}</RadioGroupItemText>
|
|
||||||
</RadioGroupItem>
|
|
||||||
</RadioGroupRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
const frameworks = ["React", "Solid", "Vue", "Svelte"];
|
|
||||||
|
|
||||||
<RadioGroupRoot defaultValue="React">
|
|
||||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
|
||||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
<RadioGroupItemText>\{\{ framework \}\}</RadioGroupItemText>
|
|
||||||
</RadioGroupItem>
|
|
||||||
</RadioGroupRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/radio-group</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as radioGroup from "@zag-js/radio-group";
|
|
||||||
import type { Props } from "@zag-js/radio-group";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { radioGroupRoot } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { Dot } from "@lucide/vue";
|
|
||||||
import { RadioGroupIndicator } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(radioGroup.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => radioGroup.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("radioGroupApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(radioGroupRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
<RadioGroupIndicator>
|
|
||||||
<Dot />
|
|
||||||
</RadioGroupIndicator>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupLabel } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { label } from "@mykopkb/core/styles/label.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/radio-group";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([label, radioGroupLabel, className])"
|
|
||||||
v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupIndicator } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/radio-group";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(radioGroupIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupItem } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { RadioGroupItemHiddenInput } from ".";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
|
||||||
import { provide, inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ItemProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
|
|
||||||
provide("radioGroupItem", props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(radioGroupItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<label v-else>
|
|
||||||
<slot />
|
|
||||||
<RadioGroupItemHiddenInput />
|
|
||||||
</label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupItemText } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(radioGroupItemText, className)"
|
|
||||||
v-bind="{ ...api?.getItemTextProps(itemProps!), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupItemControl } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(radioGroupItemControl, className)"
|
|
||||||
v-bind="{ ...api?.getItemControlProps(itemProps!), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/RadioGroupItemHiddenInput.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { radioGroupItemHiddenInput } from "@mykopkb/core/styles/radio-group.styles";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/radio-group";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("radioGroupApi");
|
|
||||||
const itemProps = inject<ItemProps>("radioGroupItem");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input :class="cn(radioGroupItemHiddenInput, className)"
|
|
||||||
v-bind="{ ...api?.getItemHiddenInputProps(itemProps!), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/radio-group/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as RadioGroupRoot } from "./RadioGroupRoot.vue";
|
|
||||||
export { default as RadioGroupLabel } from "./RadioGroupLabel.vue";
|
|
||||||
export { default as RadioGroupIndicator } from "./RadioGroupIndicator.vue";
|
|
||||||
export { default as RadioGroupItem } from "./RadioGroupItem.vue";
|
|
||||||
export { default as RadioGroupItemText } from "./RadioGroupItemText.vue";
|
|
||||||
export { default as RadioGroupItemControl } from "./RadioGroupItemControl.vue";
|
|
||||||
export { default as RadioGroupItemHiddenInput } from "./RadioGroupItemHiddenInput.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
RadioGroupRoot,
|
|
||||||
RadioGroupLabel,
|
|
||||||
RadioGroupItem,
|
|
||||||
RadioGroupItemText,
|
|
||||||
RadioGroupItemControl,
|
|
||||||
} from "@/components/ui/radio-group";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<RadioGroupRoot defaultValue="React">
|
|
||||||
<RadioGroupLabel>Framework</RadioGroupLabel>
|
|
||||||
<RadioGroupItem v-for="framework in frameworks" :key="framework" :value="framework">
|
|
||||||
<RadioGroupItemControl />
|
|
||||||
<RadioGroupItemText>\{\{ framework \}\}</RadioGroupItemText>
|
|
||||||
</RadioGroupItem>
|
|
||||||
</RadioGroupRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,259 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import {
|
|
||||||
ScrollAreaRoot,
|
|
||||||
ScrollAreaViewport,
|
|
||||||
ScrollAreaContent,
|
|
||||||
ScrollAreaScrollbar,
|
|
||||||
ScrollAreaThumb,
|
|
||||||
ScrollAreaCorner,
|
|
||||||
} from "@/components/ui/scroll-area";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<ScrollAreaRoot class="h-72 w-70">
|
|
||||||
<ScrollAreaViewport>
|
|
||||||
<ScrollAreaContent>
|
|
||||||
<div class="text-base font-medium mb-4">Scroll Area Example</div>
|
|
||||||
<div v-for="i in 20" :key="i" class="mb-4 last:mb-0 opacity-80">
|
|
||||||
This is line number {{ i }} of the scrollable content. It helps
|
|
||||||
demonstrate how the custom scrollbar works within the Midone UI
|
|
||||||
system.
|
|
||||||
</div>
|
|
||||||
</ScrollAreaContent>
|
|
||||||
</ScrollAreaViewport>
|
|
||||||
<ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaThumb />
|
|
||||||
</ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaCorner />
|
|
||||||
</ScrollAreaRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<script setup lang="ts">
|
|
||||||
import {
|
|
||||||
ScrollAreaRoot,
|
|
||||||
ScrollAreaViewport,
|
|
||||||
ScrollAreaContent,
|
|
||||||
ScrollAreaScrollbar,
|
|
||||||
ScrollAreaThumb,
|
|
||||||
ScrollAreaCorner,
|
|
||||||
} from "@/components/ui/scroll-area";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<ScrollAreaRoot class="h-72 w-70">
|
|
||||||
<ScrollAreaViewport>
|
|
||||||
<ScrollAreaContent>
|
|
||||||
<div class="text-base font-medium mb-4">Scroll Area Example</div>
|
|
||||||
<div v-for="i in 20" :key="i" class="mb-4 last:mb-0 opacity-80">
|
|
||||||
This is line number \{\{ i \}\} of the scrollable content. It helps
|
|
||||||
demonstrate how the custom scrollbar works within the Midone UI
|
|
||||||
system.
|
|
||||||
</div>
|
|
||||||
</ScrollAreaContent>
|
|
||||||
</ScrollAreaViewport>
|
|
||||||
<ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaThumb />
|
|
||||||
</ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaCorner />
|
|
||||||
</ScrollAreaRoot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/scroll-area</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as scrollArea from "@zag-js/scroll-area";
|
|
||||||
import type { Props } from "@zag-js/scroll-area";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { scrollAreaRoot } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<
|
|
||||||
Partial<Props> & {
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const service = useMachine(scrollArea.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const api = computed(() => scrollArea.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("scrollAreaApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api.getRootProps() }" :class="cn(scrollAreaRoot, className)">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaViewport.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/scroll-area";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { scrollAreaViewport } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
|
||||||
|
|
||||||
const api = inject<Api<any>>("scrollAreaApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api?.getViewportProps(), ...props }" :class="cn(scrollAreaViewport, className)">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/scroll-area";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { scrollAreaContent } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
|
||||||
|
|
||||||
const api = inject<Api<any>>("scrollAreaApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api?.getContentProps(), ...props }" :class="cn(scrollAreaContent, className)">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaScrollbar.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api, ScrollbarProps } from "@zag-js/scroll-area";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { scrollAreaScrollbar } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<
|
|
||||||
ScrollbarProps & { class?: string }
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api<any>>("scrollAreaApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api?.getScrollbarProps(), ...props }" :class="cn(scrollAreaScrollbar, className)">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaThumb.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api, ScrollbarProps } from "@zag-js/scroll-area";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { scrollAreaThumb } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<
|
|
||||||
ScrollbarProps & { class?: string }
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api<any>>("scrollAreaApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api?.getThumbProps(props), ...props }" :class="cn(scrollAreaThumb, className)" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/ScrollAreaCorner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import type { Api } from "@zag-js/scroll-area";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { scrollAreaCorner } from "@mykopkb/core/styles/scroll-area.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{ class?: string }>();
|
|
||||||
|
|
||||||
const api = inject<Api<any>>("scrollAreaApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div v-bind="{ ...api?.getCornerProps(), ...props }" :class="cn(scrollAreaCorner, className)" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/scroll-area/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as ScrollAreaRoot } from "./ScrollAreaRoot.vue";
|
|
||||||
export { default as ScrollAreaViewport } from "./ScrollAreaViewport.vue";
|
|
||||||
export { default as ScrollAreaContent } from "./ScrollAreaContent.vue";
|
|
||||||
export { default as ScrollAreaScrollbar } from "./ScrollAreaScrollbar.vue";
|
|
||||||
export { default as ScrollAreaThumb } from "./ScrollAreaThumb.vue";
|
|
||||||
export { default as ScrollAreaCorner } from "./ScrollAreaCorner.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
ScrollAreaRoot,
|
|
||||||
ScrollAreaViewport,
|
|
||||||
ScrollAreaContent,
|
|
||||||
ScrollAreaScrollbar,
|
|
||||||
ScrollAreaThumb,
|
|
||||||
ScrollAreaCorner,
|
|
||||||
} from "@/components/ui/scroll-area";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<ScrollAreaRoot class="h-72 w-70">
|
|
||||||
<ScrollAreaViewport>
|
|
||||||
<ScrollAreaContent>
|
|
||||||
<!-- Scrollable content here -->
|
|
||||||
</ScrollAreaContent>
|
|
||||||
</ScrollAreaViewport>
|
|
||||||
<ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaThumb />
|
|
||||||
</ScrollAreaScrollbar>
|
|
||||||
<ScrollAreaCorner />
|
|
||||||
</ScrollAreaRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,886 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
SelectRoot,
|
|
||||||
SelectLabel,
|
|
||||||
SelectControl,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValueText,
|
|
||||||
SelectContent,
|
|
||||||
SelectItemGroup,
|
|
||||||
SelectItemGroupLabel,
|
|
||||||
SelectItem,
|
|
||||||
SelectItemText,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
import * as select from "@zag-js/select";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
const comboboxData = [
|
|
||||||
{ label: "React", code: "react" },
|
|
||||||
{ label: "Solid", code: "solid" },
|
|
||||||
{ label: "Vue", code: "vue" },
|
|
||||||
{ label: "Svelte", code: "svelte" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const timezoneData = [
|
|
||||||
{
|
|
||||||
label: "North America",
|
|
||||||
items: [
|
|
||||||
{ value: "est", label: "Eastern Standard Time (EST)" },
|
|
||||||
{ value: "cst", label: "Central Standard Time (CST)" },
|
|
||||||
{ value: "mst", label: "Mountain Standard Time (MST)" },
|
|
||||||
{ value: "pst", label: "Pacific Standard Time (PST)" },
|
|
||||||
{ value: "akst", label: "Alaska Standard Time (AKST)" },
|
|
||||||
{ value: "hst", label: "Hawaii Standard Time (HST)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Europe & Africa",
|
|
||||||
items: [
|
|
||||||
{ value: "gmt", label: "Greenwich Mean Time (GMT)" },
|
|
||||||
{ value: "cet", label: "Central European Time (CET)" },
|
|
||||||
{ value: "eet", label: "Eastern European Time (EET)" },
|
|
||||||
{ value: "west", label: "Western European Summer Time (WEST)" },
|
|
||||||
{ value: "cat", label: "Central Africa Time (CAT)" },
|
|
||||||
{ value: "eat", label: "East Africa Time (EAT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Asia",
|
|
||||||
items: [
|
|
||||||
{ value: "msk", label: "Moscow Time (MSK)" },
|
|
||||||
{ value: "ist", label: "India Standard Time (IST)" },
|
|
||||||
{ value: "cst_china", label: "China Standard Time (CST)" },
|
|
||||||
{ value: "jst", label: "Japan Standard Time (JST)" },
|
|
||||||
{ value: "kst", label: "Korea Standard Time (KST)" },
|
|
||||||
{
|
|
||||||
value: "ist_indonesia",
|
|
||||||
label: "Indonesia Central Standard Time (WITA)",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Australia & Pacific",
|
|
||||||
items: [
|
|
||||||
{ value: "awst", label: "Australian Western Standard Time (AWST)" },
|
|
||||||
{ value: "acst", label: "Australian Central Standard Time (ACST)" },
|
|
||||||
{ value: "aest", label: "Australian Eastern Standard Time (AEST)" },
|
|
||||||
{ value: "nzst", label: "New Zealand Standard Time (NZST)" },
|
|
||||||
{ value: "fjt", label: "Fiji Time (FJT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "South America",
|
|
||||||
items: [
|
|
||||||
{ value: "art", label: "Argentina Time (ART)" },
|
|
||||||
{ value: "bot", label: "Bolivia Time (BOT)" },
|
|
||||||
{ value: "brt", label: "Brasilia Time (BRT)" },
|
|
||||||
{ value: "clt", label: "Chile Standard Time (CLT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const collection = select.collection({
|
|
||||||
items: comboboxData,
|
|
||||||
itemToValue: (item) => item.label,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionTimezone = select.collection({
|
|
||||||
items: timezoneData.flatMap((region) =>
|
|
||||||
region.items.map((item) => ({
|
|
||||||
region: region.label,
|
|
||||||
value: item.value,
|
|
||||||
label: item.label,
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
});
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SelectRoot class="w-56" :collection="collection">
|
|
||||||
<SelectLabel>Single</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Framework" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup>
|
|
||||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
|
||||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
const comboboxData = [
|
|
||||||
{ label: "React", code: "react" },
|
|
||||||
{ label: "Solid", code: "solid" },
|
|
||||||
{ label: "Vue", code: "vue" },
|
|
||||||
{ label: "Svelte", code: "svelte" },
|
|
||||||
];
|
|
||||||
|
|
||||||
const timezoneData = [
|
|
||||||
{
|
|
||||||
label: "North America",
|
|
||||||
items: [
|
|
||||||
{ value: "est", label: "Eastern Standard Time (EST)" },
|
|
||||||
{ value: "cst", label: "Central Standard Time (CST)" },
|
|
||||||
{ value: "mst", label: "Mountain Standard Time (MST)" },
|
|
||||||
{ value: "pst", label: "Pacific Standard Time (PST)" },
|
|
||||||
{ value: "akst", label: "Alaska Standard Time (AKST)" },
|
|
||||||
{ value: "hst", label: "Hawaii Standard Time (HST)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Europe & Africa",
|
|
||||||
items: [
|
|
||||||
{ value: "gmt", label: "Greenwich Mean Time (GMT)" },
|
|
||||||
{ value: "cet", label: "Central European Time (CET)" },
|
|
||||||
{ value: "eet", label: "Eastern European Time (EET)" },
|
|
||||||
{ value: "west", label: "Western European Summer Time (WEST)" },
|
|
||||||
{ value: "cat", label: "Central Africa Time (CAT)" },
|
|
||||||
{ value: "eat", label: "East Africa Time (EAT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Asia",
|
|
||||||
items: [
|
|
||||||
{ value: "msk", label: "Moscow Time (MSK)" },
|
|
||||||
{ value: "ist", label: "India Standard Time (IST)" },
|
|
||||||
{ value: "cst_china", label: "China Standard Time (CST)" },
|
|
||||||
{ value: "jst", label: "Japan Standard Time (JST)" },
|
|
||||||
{ value: "kst", label: "Korea Standard Time (KST)" },
|
|
||||||
{
|
|
||||||
value: "ist_indonesia",
|
|
||||||
label: "Indonesia Central Standard Time (WITA)",
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "Australia & Pacific",
|
|
||||||
items: [
|
|
||||||
{ value: "awst", label: "Australian Western Standard Time (AWST)" },
|
|
||||||
{ value: "acst", label: "Australian Central Standard Time (ACST)" },
|
|
||||||
{ value: "aest", label: "Australian Eastern Standard Time (AEST)" },
|
|
||||||
{ value: "nzst", label: "New Zealand Standard Time (NZST)" },
|
|
||||||
{ value: "fjt", label: "Fiji Time (FJT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
label: "South America",
|
|
||||||
items: [
|
|
||||||
{ value: "art", label: "Argentina Time (ART)" },
|
|
||||||
{ value: "bot", label: "Bolivia Time (BOT)" },
|
|
||||||
{ value: "brt", label: "Brasilia Time (BRT)" },
|
|
||||||
{ value: "clt", label: "Chile Standard Time (CLT)" },
|
|
||||||
],
|
|
||||||
},
|
|
||||||
];
|
|
||||||
|
|
||||||
const collection = select.collection({
|
|
||||||
items: comboboxData,
|
|
||||||
itemToValue: (item) => item.label,
|
|
||||||
});
|
|
||||||
|
|
||||||
const collectionTimezone = select.collection({
|
|
||||||
items: timezoneData.flatMap((region) =>
|
|
||||||
region.items.map((item) => ({
|
|
||||||
region: region.label,
|
|
||||||
value: item.value,
|
|
||||||
label: item.label,
|
|
||||||
}))
|
|
||||||
),
|
|
||||||
});
|
|
||||||
|
|
||||||
<SelectRoot class="w-56" :collection="collection">
|
|
||||||
<SelectLabel>Single</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Framework" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup>
|
|
||||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
|
||||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/select</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/select/SelectClearTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectClearTrigger } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getClearTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else :class="cn(selectClearTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectContent } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { SelectPositioner } from ".";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<SelectPositioner>
|
|
||||||
<Slot v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Box v-else raised="single" :class="cn(selectContent, className)">
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</Slot>
|
|
||||||
</SelectPositioner>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectControl } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectControl, className)" v-bind="{ ...api?.getControlProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectHiddenSelect.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectHiddenSelect } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<select :class="cn(selectHiddenSelect, className)"
|
|
||||||
v-bind="{ ...api?.getHiddenSelectProps(), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectIndicator } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { ChevronDownIcon } from "@lucide/vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectIndicator, className)" v-bind="{ ...api?.getIndicatorProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<ChevronDownIcon v-else class="size-3.5" />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectItem.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectItem } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { SelectItemIndicator } from ".";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/select";
|
|
||||||
import { provide, inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ItemProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
|
|
||||||
provide("selectItem", props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectItem, className)" v-bind="{ ...api?.getItemProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
<SelectItemIndicator />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectItemGroup.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectItemGroup } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { provide, inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
const itemGroupId = { id: crypto.randomUUID() };
|
|
||||||
|
|
||||||
provide("selectItemGroup", props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectItemGroup, className)"
|
|
||||||
v-bind="{ ...api?.getItemGroupProps(itemGroupId), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectItemGroupLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectItemGroupLabel } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemGroupProps } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
const itemGroupId = inject<ItemGroupProps>("selectItemGroup");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectItemGroupLabel, className)" v-bind="{ ...api?.getItemGroupLabelProps({
|
|
||||||
htmlFor: itemGroupId?.id!,
|
|
||||||
}), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<label v-else>
|
|
||||||
<slot />
|
|
||||||
</label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectItemIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectItemIndicator } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/select";
|
|
||||||
import { Check } from "@lucide/vue";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
const item = inject<ItemProps>("selectItem");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectItemIndicator, className)"
|
|
||||||
v-bind="{ ...api?.getItemIndicatorProps(item!), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot v-if="$slots.default" />
|
|
||||||
<Check v-else class="size-3.5" />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectItemText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectItemText } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ItemProps } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
const item = inject<ItemProps>("selectItem");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectItemText, className)" v-bind="{ ...api?.getItemTextProps(item!), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectLabel } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Label v-else :class="cn(selectLabel, className)">
|
|
||||||
<slot />
|
|
||||||
</Label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectPositioner } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectPositioner, className)" v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as select from "@zag-js/select";
|
|
||||||
import type { Props } from "@zag-js/select";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { selectRoot } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { SelectHiddenSelect } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
multiple = undefined,
|
|
||||||
open = undefined,
|
|
||||||
closeOnSelect = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(select.machine, {
|
|
||||||
...props,
|
|
||||||
multiple,
|
|
||||||
open,
|
|
||||||
closeOnSelect,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => select.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("selectApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectRoot, className)" :data-multiple="multiple"
|
|
||||||
v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
<SelectHiddenSelect />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectTrigger } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { SelectClearTrigger, SelectIndicator } from ".";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<Button variant="ghost" v-if="!asChild" :class="cn(selectTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
<SelectClearTrigger>Clear</SelectClearTrigger>
|
|
||||||
<SelectIndicator />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/SelectValueText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { selectValueText } from "@mykopkb/core/styles/select.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/select";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
placeholder?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("selectApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(selectValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>\{\{ api?.valueAsString || props.placeholder \}\}</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/select/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as SelectClearTrigger } from "./SelectClearTrigger.vue";
|
|
||||||
export { default as SelectContent } from "./SelectContent.vue";
|
|
||||||
export { default as SelectControl } from "./SelectControl.vue";
|
|
||||||
export { default as SelectHiddenSelect } from "./SelectHiddenSelect.vue";
|
|
||||||
export { default as SelectIndicator } from "./SelectIndicator.vue";
|
|
||||||
export { default as SelectItem } from "./SelectItem.vue";
|
|
||||||
export { default as SelectItemGroup } from "./SelectItemGroup.vue";
|
|
||||||
export { default as SelectItemGroupLabel } from "./SelectItemGroupLabel.vue";
|
|
||||||
export { default as SelectItemIndicator } from "./SelectItemIndicator.vue";
|
|
||||||
export { default as SelectItemText } from "./SelectItemText.vue";
|
|
||||||
export { default as SelectLabel } from "./SelectLabel.vue";
|
|
||||||
export { default as SelectPositioner } from "./SelectPositioner.vue";
|
|
||||||
export { default as SelectRoot } from "./SelectRoot.vue";
|
|
||||||
export { default as SelectTrigger } from "./SelectTrigger.vue";
|
|
||||||
export { default as SelectValueText } from "./SelectValueText.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
SelectRoot,
|
|
||||||
SelectLabel,
|
|
||||||
SelectControl,
|
|
||||||
SelectTrigger,
|
|
||||||
SelectValueText,
|
|
||||||
SelectContent,
|
|
||||||
SelectItemGroup,
|
|
||||||
SelectItemGroupLabel,
|
|
||||||
SelectItem,
|
|
||||||
SelectItemText,
|
|
||||||
} from "@/components/ui/select";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SelectRoot class="w-56" :collection="collection">
|
|
||||||
<SelectLabel>Single</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Framework" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup>
|
|
||||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
|
||||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SelectRoot class="w-56" :collection="collection" multiple>
|
|
||||||
<SelectLabel>Multiple</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Framework" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup>
|
|
||||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
|
||||||
<SelectItemText>{{ item.label }}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SelectRoot class="w-56" :collection="collection" multiple>
|
|
||||||
<SelectLabel>Multiple</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Framework" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup>
|
|
||||||
<SelectItemGroupLabel> Frameworks </SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="item in collection.items" :key="item.code" :item="item">
|
|
||||||
<SelectItemText>\{\{ item.label \}\}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SelectRoot class="w-56" :collection="collectionTimezone" multiple>
|
|
||||||
<SelectLabel>Scrollable</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Timezone" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup v-for="item in timezoneData" :key="item.label">
|
|
||||||
<SelectItemGroupLabel>{{ item.label }}</SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="timezoneItem in item.items" :key="timezoneItem.value" :item="timezoneItem.value">
|
|
||||||
<SelectItemText>{{ timezoneItem.label }}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SelectRoot class="w-56" :collection="collectionTimezone" multiple>
|
|
||||||
<SelectLabel>Scrollable</SelectLabel>
|
|
||||||
<SelectControl>
|
|
||||||
<SelectTrigger>
|
|
||||||
<SelectValueText placeholder="Select a Timezone" />
|
|
||||||
</SelectTrigger>
|
|
||||||
</SelectControl>
|
|
||||||
<SelectContent>
|
|
||||||
<SelectItemGroup v-for="item in timezoneData" :key="item.label">
|
|
||||||
<SelectItemGroupLabel>\{\{ item.label \}\}</SelectItemGroupLabel>
|
|
||||||
<SelectItem v-for="timezoneItem in item.items" :key="timezoneItem.value" :item="timezoneItem.value">
|
|
||||||
<SelectItemText>\{\{ timezoneItem.label \}\}</SelectItemText>
|
|
||||||
</SelectItem>
|
|
||||||
</SelectItemGroup>
|
|
||||||
</SelectContent>
|
|
||||||
</SelectRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,508 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
SheetRoot,
|
|
||||||
SheetTrigger,
|
|
||||||
SheetContent,
|
|
||||||
SheetTitle,
|
|
||||||
SheetDescription,
|
|
||||||
SheetCloseTrigger,
|
|
||||||
} from "@/components/ui/sheet";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SheetRoot>
|
|
||||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
|
||||||
<SheetContent>
|
|
||||||
<SheetTitle>Sheet Title</SheetTitle>
|
|
||||||
<SheetDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</SheetDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<SheetCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</SheetCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<SheetCloseTrigger />
|
|
||||||
</SheetContent>
|
|
||||||
</SheetRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SheetRoot>
|
|
||||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
|
||||||
<SheetContent>
|
|
||||||
<SheetTitle>Sheet Title</SheetTitle>
|
|
||||||
<SheetDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</SheetDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<SheetCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</SheetCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<SheetCloseTrigger />
|
|
||||||
</SheetContent>
|
|
||||||
</SheetRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/dialog</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { provide, computed } from "vue";
|
|
||||||
import * as dialog from "@zag-js/dialog";
|
|
||||||
import type { Props } from "@zag-js/dialog";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
open = undefined,
|
|
||||||
closeOnInteractOutside = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
Partial<Props> & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const service = useMachine(dialog.machine, {
|
|
||||||
...props,
|
|
||||||
get open() {
|
|
||||||
return open;
|
|
||||||
},
|
|
||||||
closeOnInteractOutside,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
|
|
||||||
const api = computed(() => dialog.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("sheetApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetTrigger } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps() }">
|
|
||||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(sheetTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetBackdrop.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetBackdrop } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sheetBackdrop, className)" v-bind="{ ...props, ...$attrs, ...api?.getBackdropProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetPositioner } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sheetPositioner, className)" v-bind="{ ...props, ...$attrs, ...api?.getPositionerProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetContent } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { SheetBackdrop, SheetPositioner } from "@/components/ui/sheet";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
side = "right",
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
side?: "top" | "right" | "bottom" | "left";
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<SheetBackdrop />
|
|
||||||
<SheetPositioner>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getContentProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<Box raised="double" :data-side="side" :class="cn(sheetContent, className)" v-bind="{ ...props }">
|
|
||||||
<div>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</SheetPositioner>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetTitle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetTitle } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sheetTitle, className)" v-bind="{ ...props, ...$attrs, ...api?.getTitleProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetDescription } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sheetDescription, className)" v-bind="{ ...props, ...$attrs, ...api?.getDescriptionProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/SheetCloseTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sheetCloseTrigger } from "@mykopkb/core/styles/sheet.styles";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import type { Api } from "@zag-js/dialog";
|
|
||||||
import {
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonVariants,
|
|
||||||
} from "@mykopkb/core/styles/button.styles";
|
|
||||||
import { X } from "@lucide/vue";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
look = "outline",
|
|
||||||
variant = "secondary",
|
|
||||||
size,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ButtonVariants & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sheetApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...props, ...$attrs, ...api?.getCloseTriggerProps() }">
|
|
||||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(sheetCloseTrigger, className)"
|
|
||||||
v-bind="{ ...props }">
|
|
||||||
<X class="size-4" />
|
|
||||||
</Button>
|
|
||||||
<template v-else>
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Button v-else :class="
|
|
||||||
cn(buttonVariants({ look, variant, size, className }), className)
|
|
||||||
">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/sheet/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as SheetRoot } from "./SheetRoot.vue";
|
|
||||||
export { default as SheetTrigger } from "./SheetTrigger.vue";
|
|
||||||
export { default as SheetBackdrop } from "./SheetBackdrop.vue";
|
|
||||||
export { default as SheetPositioner } from "./SheetPositioner.vue";
|
|
||||||
export { default as SheetContent } from "./SheetContent.vue";
|
|
||||||
export { default as SheetTitle } from "./SheetTitle.vue";
|
|
||||||
export { default as SheetDescription } from "./SheetDescription.vue";
|
|
||||||
export { default as SheetCloseTrigger } from "./SheetCloseTrigger.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
SheetRoot,
|
|
||||||
SheetTrigger,
|
|
||||||
SheetContent,
|
|
||||||
SheetTitle,
|
|
||||||
SheetDescription,
|
|
||||||
SheetCloseTrigger,
|
|
||||||
} from "@/components/ui/sheet";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SheetRoot>
|
|
||||||
<SheetTrigger>Open Sheet</SheetTrigger>
|
|
||||||
<SheetContent>
|
|
||||||
<SheetTitle>Sheet Title</SheetTitle>
|
|
||||||
<SheetDescription>
|
|
||||||
Make changes to your profile here. Click save when you're done.
|
|
||||||
</SheetDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<SheetCloseTrigger>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</SheetCloseTrigger>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
<SheetCloseTrigger />
|
|
||||||
</SheetContent>
|
|
||||||
</SheetRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SheetRoot>
|
|
||||||
<SheetTrigger>Custom Close</SheetTrigger>
|
|
||||||
<SheetContent>
|
|
||||||
<SheetTitle>Share Link</SheetTitle>
|
|
||||||
<SheetDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</SheetDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<SheetCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</SheetCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</SheetContent>
|
|
||||||
</SheetRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SheetRoot>
|
|
||||||
<SheetTrigger>Custom Close</SheetTrigger>
|
|
||||||
<SheetContent>
|
|
||||||
<SheetTitle>Share Link</SheetTitle>
|
|
||||||
<SheetDescription>
|
|
||||||
Anyone who has this link will be able to view this.
|
|
||||||
</SheetDescription>
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<SheetCloseTrigger>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</SheetCloseTrigger>
|
|
||||||
</div>
|
|
||||||
</SheetContent>
|
|
||||||
</SheetRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,493 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
SliderRoot,
|
|
||||||
SliderLabel,
|
|
||||||
SliderValueText,
|
|
||||||
SliderControl,
|
|
||||||
SliderTrack,
|
|
||||||
SliderRange,
|
|
||||||
SliderThumb,
|
|
||||||
SliderHiddenInput,
|
|
||||||
SliderMarkerGroup,
|
|
||||||
SliderMarker,
|
|
||||||
} from "@/components/ui/slider";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
|
||||||
<SliderLabel>Max Items</SliderLabel>
|
|
||||||
<SliderControl>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderRange />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb :index="0">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
</SliderControl>
|
|
||||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
|
||||||
<SliderValueText /> Items
|
|
||||||
</div>
|
|
||||||
</SliderRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
|
||||||
<SliderLabel>Max Items</SliderLabel>
|
|
||||||
<SliderControl>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderRange />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb :index="0">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
</SliderControl>
|
|
||||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
|
||||||
<SliderValueText /> Items
|
|
||||||
</div>
|
|
||||||
</SliderRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/slider</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as slider from "@zag-js/slider";
|
|
||||||
import type { Props } from "@zag-js/slider";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { sliderRoot } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(slider.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => slider.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("sliderApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderRoot, className)" v-bind="{ ...props, ...$attrs, ...api.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderLabel } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getLabelProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Label v-else :class="cn(sliderLabel, className)">
|
|
||||||
<slot />
|
|
||||||
</Label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderValueText.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderValueText } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderValueText, className)" v-bind="{ ...api?.getValueTextProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<output v-else>\{\{ api?.value?.[0] \}\}</output>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderControl } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderControl, className)" v-bind="{ ...api?.getControlProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderTrack.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderTrack } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderTrack, className)" v-bind="{ ...api?.getTrackProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderRange.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderRange } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderRange, className)" v-bind="{ ...api?.getRangeProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderThumb.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderThumb } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, ThumbProps } from "@zag-js/slider";
|
|
||||||
import { provide, inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ThumbProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
|
|
||||||
provide("sliderThumb", props);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderThumb, className)" v-bind="{ ...api?.getThumbProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderHiddenInput.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderHiddenInput } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import type { Api, ThumbProps } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
const thumbProps = inject<ThumbProps>("sliderThumb");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input :class="cn(sliderHiddenInput, className)"
|
|
||||||
v-bind="{ ...api?.getHiddenInputProps(thumbProps!), ...props, ...$attrs }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderMarkerGroup.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderMarkerGroup } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderMarkerGroup, className)" v-bind="{ ...api?.getMarkerGroupProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/SliderMarker.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { sliderMarker } from "@mykopkb/core/styles/slider.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api, MarkerProps } from "@zag-js/slider";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
MarkerProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("sliderApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(sliderMarker, className)" v-bind="{ ...api?.getMarkerProps(props), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slider/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as SliderRoot } from "./SliderRoot.vue";
|
|
||||||
export { default as SliderLabel } from "./SliderLabel.vue";
|
|
||||||
export { default as SliderValueText } from "./SliderValueText.vue";
|
|
||||||
export { default as SliderControl } from "./SliderControl.vue";
|
|
||||||
export { default as SliderTrack } from "./SliderTrack.vue";
|
|
||||||
export { default as SliderRange } from "./SliderRange.vue";
|
|
||||||
export { default as SliderThumb } from "./SliderThumb.vue";
|
|
||||||
export { default as SliderHiddenInput } from "./SliderHiddenInput.vue";
|
|
||||||
export { default as SliderMarkerGroup } from "./SliderMarkerGroup.vue";
|
|
||||||
export { default as SliderMarker } from "./SliderMarker.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
SliderRoot,
|
|
||||||
SliderLabel,
|
|
||||||
SliderValueText,
|
|
||||||
SliderControl,
|
|
||||||
SliderTrack,
|
|
||||||
SliderRange,
|
|
||||||
SliderThumb,
|
|
||||||
SliderHiddenInput,
|
|
||||||
SliderMarkerGroup,
|
|
||||||
SliderMarker,
|
|
||||||
} from "@/components/ui/slider";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SliderRoot class="w-72" :defaultValue="[20]">
|
|
||||||
<SliderLabel>Max Items</SliderLabel>
|
|
||||||
<SliderControl>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderRange />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb :index="0">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
</SliderControl>
|
|
||||||
<div class="flex items-center text-xs gap-1 font-medium justify-center opacity-70">
|
|
||||||
<SliderValueText /> Items
|
|
||||||
</div>
|
|
||||||
</SliderRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SliderRoot class="w-72" :value="[20, 80]">
|
|
||||||
<SliderLabel>Price Range</SliderLabel>
|
|
||||||
<SliderControl>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderRange />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb :index="0">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
<SliderThumb :index="1">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
</SliderControl>
|
|
||||||
<SliderMarkerGroup>
|
|
||||||
<SliderMarker :value="0">$0</SliderMarker>
|
|
||||||
<SliderMarker :value="25">$25</SliderMarker>
|
|
||||||
<SliderMarker :value="50">$50</SliderMarker>
|
|
||||||
<SliderMarker :value="75">$75</SliderMarker>
|
|
||||||
<SliderMarker :value="100">$100</SliderMarker>
|
|
||||||
</SliderMarkerGroup>
|
|
||||||
</SliderRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SliderRoot class="w-72" :value="[20, 80]">
|
|
||||||
<SliderLabel>Price Range</SliderLabel>
|
|
||||||
<SliderControl>
|
|
||||||
<SliderTrack>
|
|
||||||
<SliderRange />
|
|
||||||
</SliderTrack>
|
|
||||||
<SliderThumb :index="0">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
<SliderThumb :index="1">
|
|
||||||
<SliderHiddenInput />
|
|
||||||
</SliderThumb>
|
|
||||||
</SliderControl>
|
|
||||||
<SliderMarkerGroup>
|
|
||||||
<SliderMarker :value="0">$0</SliderMarker>
|
|
||||||
<SliderMarker :value="25">$25</SliderMarker>
|
|
||||||
<SliderMarker :value="50">$50</SliderMarker>
|
|
||||||
<SliderMarker :value="75">$75</SliderMarker>
|
|
||||||
<SliderMarker :value="100">$100</SliderMarker>
|
|
||||||
</SliderMarkerGroup>
|
|
||||||
</SliderRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,196 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { ChevronLeft, ChevronRight } from "@lucide/vue";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<div class="justify-center items-center flex gap-2">
|
|
||||||
<Box as-child>
|
|
||||||
<Button variant="ghost" class="me-2 px-2">
|
|
||||||
<ChevronLeft class="size-5" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Box as-child>
|
|
||||||
<Button variant="ghost" class="px-2">
|
|
||||||
<ChevronRight class="size-5" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<div class="justify-center items-center flex gap-2">
|
|
||||||
<Box as-child>
|
|
||||||
<Button variant="ghost" class="me-2 px-2">
|
|
||||||
<ChevronLeft class="size-5" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
<Box as-child>
|
|
||||||
<Button variant="ghost" class="px-2">
|
|
||||||
<ChevronRight class="size-5" />
|
|
||||||
</Button>
|
|
||||||
</Box>
|
|
||||||
</div>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
By using <code>Slot</code> at the root, you ensure that props are correctly merged whether
|
|
||||||
you're using it as a direct wrapper or an <code>asChild</code> bridge.
|
|
||||||
</SectionContent>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
|
|
||||||
<PreviewCode title="components/ui/slot/index.ts">
|
|
||||||
{{ `import {
|
|
||||||
defineComponent,
|
|
||||||
h,
|
|
||||||
cloneVNode,
|
|
||||||
Fragment,
|
|
||||||
isVNode,
|
|
||||||
type VNode,
|
|
||||||
type PropType,
|
|
||||||
} from "vue";
|
|
||||||
import { calculateSlot, flattenItems, type AnyProps } from "./slot";
|
|
||||||
|
|
||||||
export const Slot = defineComponent({
|
|
||||||
name: "Slot",
|
|
||||||
inheritAttrs: false,
|
|
||||||
props: {
|
|
||||||
children: {
|
|
||||||
type: [Object, Array] as PropType<any>,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
setup(props, { attrs, slots }) {
|
|
||||||
return () => {
|
|
||||||
const raw = props.children ?? slots.default?.();
|
|
||||||
const isValidVNode = (item: any): item is VNode => isVNode(item) && typeof item.type !== "symbol";
|
|
||||||
|
|
||||||
const items = flattenItems<VNode>(
|
|
||||||
raw as any,
|
|
||||||
(item) => isVNode(item) && item.type === Fragment,
|
|
||||||
(item) => (isVNode(item) && Array.isArray(item.children) ? (item.children as VNode[]) : [])
|
|
||||||
).filter(isValidVNode);
|
|
||||||
|
|
||||||
const result = calculateSlot<VNode>({
|
|
||||||
props: attrs as AnyProps,
|
|
||||||
items,
|
|
||||||
isValid: isValidVNode,
|
|
||||||
getProps: (item) => (item.props as AnyProps) || {},
|
|
||||||
getChildren: (item) => item.children,
|
|
||||||
});
|
|
||||||
|
|
||||||
if (result.type === "wrapper") {
|
|
||||||
return h("div", result.props, result.children as any);
|
|
||||||
}
|
|
||||||
|
|
||||||
const target = result.target;
|
|
||||||
return cloneVNode(target, result.props, false);
|
|
||||||
};
|
|
||||||
},
|
|
||||||
});
|
|
||||||
|
|
||||||
export { Slot as Root };` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/slot/slot.ts">
|
|
||||||
{{ `export type AnyProps = Record<string, any>;
|
|
||||||
|
|
||||||
export interface SlotParams<T> {
|
|
||||||
props: AnyProps;
|
|
||||||
items: T[];
|
|
||||||
isValid: (item: T) => boolean;
|
|
||||||
getProps: (item: T) => AnyProps;
|
|
||||||
getChildren: (item: T) => any;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type SlotResult<T> =
|
|
||||||
| { type: "slotted"; target: T; props: AnyProps; children: any }
|
|
||||||
| { type: "wrapper"; target: "div"; props: AnyProps; children: T[] };
|
|
||||||
|
|
||||||
export function mergeProps(slotProps: AnyProps, childProps: AnyProps): AnyProps {
|
|
||||||
const result: AnyProps = { ...childProps };
|
|
||||||
for (const key in slotProps) {
|
|
||||||
const slotValue = slotProps[key];
|
|
||||||
const isHandler = /^on[A-Z]/.test(key);
|
|
||||||
if (isHandler) {
|
|
||||||
const childValue = childProps[key];
|
|
||||||
if (typeof slotValue === "function" && typeof childValue === "function") {
|
|
||||||
result[key] = (...args: any[]) => {
|
|
||||||
childValue(...args);
|
|
||||||
slotValue(...args);
|
|
||||||
};
|
|
||||||
} else if (slotValue) {
|
|
||||||
result[key] = slotValue;
|
|
||||||
}
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (key === "class" || key === "className") {
|
|
||||||
const slotClasses = (slotValue || "").split(/\\s+/);
|
|
||||||
const childClasses = (childProps.class || childProps.className || "").split(/\\s+/);
|
|
||||||
const combined = Array.from(new Set([...slotClasses, ...childClasses])).filter(Boolean).join(" ");
|
|
||||||
result[key] = combined;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (key === "style") {
|
|
||||||
result[key] = { ...slotValue, ...childProps.style };
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
if (childProps[key] === undefined) {
|
|
||||||
result[key] = slotValue;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function flattenItems<T>(items: T | T[], isFragment: (item: T) => boolean, getChildren: (item: T) =>
|
|
||||||
T
|
|
||||||
| T[]): T[] {
|
|
||||||
const result: T[] = [];
|
|
||||||
const list = Array.isArray(items) ? items : [items];
|
|
||||||
list.forEach((item) => {
|
|
||||||
if (item === null || item === undefined) return;
|
|
||||||
if (isFragment(item)) {
|
|
||||||
const children = getChildren(item);
|
|
||||||
result.push(...flattenItems(children, isFragment, getChildren));
|
|
||||||
} else {
|
|
||||||
result.push(item);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function calculateSlot<T>(params: SlotParams<T>): SlotResult<T> {
|
|
||||||
const { props, items, isValid, getProps, getChildren } = params;
|
|
||||||
if (items.length === 1) {
|
|
||||||
const primary = items[0];
|
|
||||||
if (isValid(primary)) {
|
|
||||||
return {
|
|
||||||
type: "slotted",
|
|
||||||
target: primary,
|
|
||||||
props: mergeProps(props, getProps(primary)),
|
|
||||||
children: getChildren(primary),
|
|
||||||
};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return { type: "wrapper", target: "div", props: props, children: items };
|
|
||||||
}` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,230 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { SwitchRoot, SwitchControl, SwitchLabel } from "@/components/ui/switch";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<SwitchRoot>
|
|
||||||
<SwitchControl />
|
|
||||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
|
||||||
</SwitchRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SwitchRoot>
|
|
||||||
<SwitchControl />
|
|
||||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
|
||||||
</SwitchRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/switch</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/switch/SwitchRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as zagSwitch from "@zag-js/switch";
|
|
||||||
import type { Props } from "@zag-js/switch";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { switchRoot } from "@mykopkb/core/styles/switch.styles";
|
|
||||||
import { SwitchHiddenInput } from ".";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
checked = undefined,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(zagSwitch.machine, {
|
|
||||||
...props,
|
|
||||||
checked,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => zagSwitch.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("switchApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(switchRoot, className)" v-bind="{ ...props, ...$attrs, ...api?.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<label v-else>
|
|
||||||
<slot />
|
|
||||||
<SwitchHiddenInput />
|
|
||||||
</label>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/switch/SwitchControl.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { switchControl } from "@mykopkb/core/styles/switch.styles";
|
|
||||||
import { SwitchThumb } from ".";
|
|
||||||
import type { Api } from "@zag-js/switch";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("switchApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(switchControl, className)" v-bind="{ ...props, ...$attrs, ...api?.getControlProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<SwitchThumb />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/switch/SwitchThumb.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { switchThumb } from "@mykopkb/core/styles/switch.styles";
|
|
||||||
import type { Api } from "@zag-js/switch";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("switchApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(switchThumb, className)" v-bind="{ ...props, ...$attrs, ...api?.getThumbProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/switch/SwitchLabel.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { switchLabel } from "@mykopkb/core/styles/switch.styles";
|
|
||||||
import { label } from "@mykopkb/core/styles/label.styles";
|
|
||||||
import type { Api } from "@zag-js/switch";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("switchApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([label, switchLabel, className])" v-bind="{ ...props, ...$attrs, ...api?.getLabelProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<span v-else>
|
|
||||||
<slot />
|
|
||||||
</span>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/switch/SwitchHiddenInput.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { switchHiddenInput } from "@mykopkb/core/styles/switch.styles";
|
|
||||||
import type { Api } from "@zag-js/switch";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("switchApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<input :class="cn(switchHiddenInput, className)"
|
|
||||||
v-bind="{ ...props, ...$attrs, ...api?.getHiddenInputProps() }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/switch/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as SwitchRoot } from "./SwitchRoot.vue";
|
|
||||||
export { default as SwitchControl } from "./SwitchControl.vue";
|
|
||||||
export { default as SwitchThumb } from "./SwitchThumb.vue";
|
|
||||||
export { default as SwitchLabel } from "./SwitchLabel.vue";
|
|
||||||
export { default as SwitchHiddenInput } from "./SwitchHiddenInput.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { SwitchRoot, SwitchControl, SwitchLabel } from "@/components/ui/switch";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<SwitchRoot>
|
|
||||||
<SwitchControl />
|
|
||||||
<SwitchLabel>Airplane Mode</SwitchLabel>
|
|
||||||
</SwitchRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,653 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
import { Badge } from "@/components/ui/badge";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
const invoices: {
|
|
||||||
invoice: string;
|
|
||||||
paymentStatus: string;
|
|
||||||
badge: "success" | "pending" | "danger";
|
|
||||||
totalAmount: string;
|
|
||||||
paymentMethod: string;
|
|
||||||
}[] = [
|
|
||||||
{
|
|
||||||
invoice: "INV001",
|
|
||||||
paymentStatus: "Paid",
|
|
||||||
badge: "success",
|
|
||||||
totalAmount: "$250.00",
|
|
||||||
paymentMethod: "Credit Card",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV002",
|
|
||||||
paymentStatus: "Pending",
|
|
||||||
badge: "pending",
|
|
||||||
totalAmount: "$150.00",
|
|
||||||
paymentMethod: "PayPal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV003",
|
|
||||||
paymentStatus: "Unpaid",
|
|
||||||
badge: "danger",
|
|
||||||
totalAmount: "$350.00",
|
|
||||||
paymentMethod: "Bank Transfer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV004",
|
|
||||||
paymentStatus: "Paid",
|
|
||||||
badge: "success",
|
|
||||||
totalAmount: "$450.00",
|
|
||||||
paymentMethod: "Credit Card",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV005",
|
|
||||||
paymentStatus: "Paid",
|
|
||||||
badge: "success",
|
|
||||||
totalAmount: "$550.00",
|
|
||||||
paymentMethod: "PayPal",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV006",
|
|
||||||
paymentStatus: "Pending",
|
|
||||||
badge: "pending",
|
|
||||||
totalAmount: "$200.00",
|
|
||||||
paymentMethod: "Bank Transfer",
|
|
||||||
},
|
|
||||||
{
|
|
||||||
invoice: "INV007",
|
|
||||||
paymentStatus: "Unpaid",
|
|
||||||
badge: "danger",
|
|
||||||
totalAmount: "$300.00",
|
|
||||||
paymentMethod: "Credit Card",
|
|
||||||
},
|
|
||||||
];
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Table>
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
{{ invoice.invoice }}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
{{ invoice.paymentStatus }}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{{ invoice.paymentMethod }}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
{{ invoice.totalAmount }}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Table>
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
\{\{ invoice.invoice \}\}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
\{\{ invoice.paymentStatus \}\}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>\{\{ invoice.paymentMethod \}\}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
\{\{ invoice.totalAmount \}\}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/table/Table.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import {
|
|
||||||
tableVariants,
|
|
||||||
type TableVariants,
|
|
||||||
} from "@mykopkb/core/styles/table.styles";
|
|
||||||
import { provide, computed } from "vue";
|
|
||||||
import TableContainer from "./TableContainer.vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<
|
|
||||||
TableVariants & {
|
|
||||||
class?: string;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
provide(
|
|
||||||
"tableVariant",
|
|
||||||
computed(() => ({
|
|
||||||
variant: props.variant,
|
|
||||||
raised: props.raised,
|
|
||||||
}))
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<TableContainer>
|
|
||||||
<table :class="cn(tableVariants({ variant, raised, className }), className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</table>
|
|
||||||
</TableContainer>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableContainer.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableContainer } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div :class="cn(tableContainer, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableHeader.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableHeader } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<thead :class="cn(tableHeader, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</thead>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableBody.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableBody } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<tbody :class="cn(tableBody, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</tbody>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableFooter.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableFooter } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<tfoot :class="cn(tableFooter, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</tfoot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableHead.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableHead } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<th :class="cn(tableHead, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</th>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableRow.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableRow } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<tr :class="cn(tableRow, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</tr>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableCell.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableCellVariants } from "@mykopkb/core/styles/table.styles";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const variant = inject<
|
|
||||||
| {
|
|
||||||
variant?: "default" | "boxed" | null;
|
|
||||||
raised?: "single" | "double" | null;
|
|
||||||
}
|
|
||||||
| undefined
|
|
||||||
>("tableVariant", undefined);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<td :class="cn(tableCellVariants({ ...variant, className }), className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</td>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/TableCaption.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tableCaption } from "@mykopkb/core/styles/table.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<caption :class="cn(tableCaption, className)" v-bind="{ ...props }">
|
|
||||||
<slot />
|
|
||||||
</caption>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/table/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Table } from "./Table.vue";
|
|
||||||
export { default as TableContainer } from "./TableContainer.vue";
|
|
||||||
export { default as TableHeader } from "./TableHeader.vue";
|
|
||||||
export { default as TableBody } from "./TableBody.vue";
|
|
||||||
export { default as TableFooter } from "./TableFooter.vue";
|
|
||||||
export { default as TableHead } from "./TableHead.vue";
|
|
||||||
export { default as TableRow } from "./TableRow.vue";
|
|
||||||
export { default as TableCell } from "./TableCell.vue";
|
|
||||||
export { default as TableCaption } from "./TableCaption.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
Table,
|
|
||||||
TableHeader,
|
|
||||||
TableBody,
|
|
||||||
TableFooter,
|
|
||||||
TableHead,
|
|
||||||
TableRow,
|
|
||||||
TableCell,
|
|
||||||
TableCaption,
|
|
||||||
} from "@/components/ui/table";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Table>
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
\{\{ invoice.invoice \}\}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
\{\{ invoice.paymentStatus \}\}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>\{\{ invoice.paymentMethod \}\}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
\{\{ invoice.totalAmount \}\}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
<div id="variants">
|
|
||||||
<SectionTitle>Variants</SectionTitle>
|
|
||||||
<SectionContent>A collection of components you can use.</SectionContent>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Table variant="boxed">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
{{ invoice.invoice }}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
{{ invoice.paymentStatus }}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{{ invoice.paymentMethod }}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
{{ invoice.totalAmount }}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Table variant="boxed">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
\{\{ invoice.invoice \}\}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
\{\{ invoice.paymentStatus \}\}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>\{\{ invoice.paymentMethod \}\}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
\{\{ invoice.totalAmount \}\}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Table variant="boxed" raised="single">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
{{ invoice.invoice }}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
{{ invoice.paymentStatus }}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{{ invoice.paymentMethod }}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
{{ invoice.totalAmount }}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Table variant="boxed" raised="single">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
\{\{ invoice.invoice \}\}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
\{\{ invoice.paymentStatus \}\}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>\{\{ invoice.paymentMethod \}\}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
\{\{ invoice.totalAmount \}\}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Table variant="boxed" raised="double">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
{{ invoice.invoice }}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
{{ invoice.paymentStatus }}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>{{ invoice.paymentMethod }}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
{{ invoice.totalAmount }}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Table variant="boxed" raised="double">
|
|
||||||
<TableCaption>A list of your recent invoices.</TableCaption>
|
|
||||||
<TableHeader>
|
|
||||||
<TableRow>
|
|
||||||
<TableHead class="w-[100px]">Invoice</TableHead>
|
|
||||||
<TableHead>Status</TableHead>
|
|
||||||
<TableHead>Method</TableHead>
|
|
||||||
<TableHead class="text-right">Amount</TableHead>
|
|
||||||
</TableRow>
|
|
||||||
</TableHeader>
|
|
||||||
<TableBody>
|
|
||||||
<TableRow v-for="invoice in invoices" :key="invoice.invoice">
|
|
||||||
<TableCell class="font-medium">
|
|
||||||
\{\{ invoice.invoice \}\}
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>
|
|
||||||
<Badge :variant="invoice.badge">
|
|
||||||
\{\{ invoice.paymentStatus \}\}
|
|
||||||
</Badge>
|
|
||||||
</TableCell>
|
|
||||||
<TableCell>\{\{ invoice.paymentMethod \}\}</TableCell>
|
|
||||||
<TableCell class="text-right">
|
|
||||||
\{\{ invoice.totalAmount \}\}
|
|
||||||
</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableBody>
|
|
||||||
<TableFooter>
|
|
||||||
<TableRow>
|
|
||||||
<TableCell :colspan="3">Total</TableCell>
|
|
||||||
<TableCell class="text-right">$2,500.00</TableCell>
|
|
||||||
</TableRow>
|
|
||||||
</TableFooter>
|
|
||||||
</Table>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,361 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Box } from "@/components/ui/box";
|
|
||||||
import {
|
|
||||||
TabsRoot,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
TabsContent,
|
|
||||||
} from "@/components/ui/tabs";
|
|
||||||
import { Label } from "@/components/ui/label";
|
|
||||||
import { Input } from "@/components/ui/input";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { SquareX, Save, ExternalLink } from "@lucide/vue";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<TabsRoot defaultValue="update-profile">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
|
||||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<Box raised="single" class="w-90">
|
|
||||||
<TabsContent value="update-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<Button>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="share-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<Button>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Box>
|
|
||||||
</TabsRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<TabsRoot defaultValue="update-profile">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
|
||||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<Box raised="single" class="w-90">
|
|
||||||
<TabsContent value="update-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<Button>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="share-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<Button>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Box>
|
|
||||||
</TabsRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/react @zag-js/tabs</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/tabs/TabsContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tabsContent } from "@mykopkb/core/styles/tabs.styles";
|
|
||||||
import type { Api, ContentProps } from "@zag-js/tabs";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ContentProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tabsApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tabsContent, className)" v-bind="{ ...props, ...$attrs, ...api?.getContentProps(props) }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tabs/TabsIndicator.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tabsIndicator } from "@mykopkb/core/styles/tabs.styles";
|
|
||||||
import type { Api } from "@zag-js/tabs";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tabsApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tabsIndicator, className)" v-bind="{ ...props, ...$attrs, ...api?.getIndicatorProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tabs/TabsList.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tabsList } from "@mykopkb/core/styles/tabs.styles";
|
|
||||||
import type { Api } from "@zag-js/tabs";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { TabsIndicator } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tabsApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tabsList, className)" v-bind="{ ...props, ...$attrs, ...api?.getListProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
<TabsIndicator />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tabs/TabsRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as tabs from "@zag-js/tabs";
|
|
||||||
import type { Props } from "@zag-js/tabs";
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tabsRoot } from "@mykopkb/core/styles/tabs.styles";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(tabs.machine, {
|
|
||||||
...props,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => tabs.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("tabsApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tabsRoot, className)" v-bind="{ ...props, ...$attrs, ...api?.getRootProps() }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tabs/TabsTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tabsTrigger } from "@mykopkb/core/styles/tabs.styles";
|
|
||||||
import type { Api, TriggerProps } from "@zag-js/tabs";
|
|
||||||
import { inject } from "vue";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
TriggerProps & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tabsApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tabsTrigger, className)" v-bind="{ ...props, ...$attrs, ...api?.getTriggerProps(props) }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<button v-else>
|
|
||||||
<slot />
|
|
||||||
</button>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tabs/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as TabsContent } from "./TabsContent.vue";
|
|
||||||
export { default as TabsIndicator } from "./TabsIndicator.vue";
|
|
||||||
export { default as TabsList } from "./TabsList.vue";
|
|
||||||
export { default as TabsRoot } from "./TabsRoot.vue";
|
|
||||||
export { default as TabsTrigger } from "./TabsTrigger.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
TabsRoot,
|
|
||||||
TabsList,
|
|
||||||
TabsTrigger,
|
|
||||||
TabsContent,
|
|
||||||
} from "@/components/ui/tabs";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<TabsRoot defaultValue="update-profile">
|
|
||||||
<TabsList>
|
|
||||||
<TabsTrigger value="update-profile"> Update Profile </TabsTrigger>
|
|
||||||
<TabsTrigger value="share-profile"> Share Profile </TabsTrigger>
|
|
||||||
</TabsList>
|
|
||||||
<Box raised="single" class="w-90">
|
|
||||||
<TabsContent value="update-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="name-1">Name</Label>
|
|
||||||
<Input id="name-1" name="name" defaultValue="Pedro Duarte" />
|
|
||||||
</div>
|
|
||||||
<div class="grid gap-2.5">
|
|
||||||
<Label for="username-1">Username</Label>
|
|
||||||
<Input id="username-1" name="username" defaultValue="@peduarte" />
|
|
||||||
</div>
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 justify-end mt-7">
|
|
||||||
<Button>
|
|
||||||
<SquareX />
|
|
||||||
Close
|
|
||||||
</Button>
|
|
||||||
<Button variant="primary">
|
|
||||||
<Save />
|
|
||||||
Submit
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
<TabsContent value="share-profile">
|
|
||||||
<div class="grid gap-4 mt-2">
|
|
||||||
<Input id="name-1" name="name" defaultValue="https://midone-ui.com/docs/installation" />
|
|
||||||
</div>
|
|
||||||
<div class="flex gap-2 mt-5">
|
|
||||||
<Button>
|
|
||||||
<ExternalLink />
|
|
||||||
Share Link
|
|
||||||
</Button>
|
|
||||||
</div>
|
|
||||||
</TabsContent>
|
|
||||||
</Box>
|
|
||||||
</TabsRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,69 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Textarea class="w-86" placeholder="Type your message here." />
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Textarea class="w-86" placeholder="Type your message here." />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/textarea/Textarea.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { textarea } from "@mykopkb/core/styles/textarea.styles";
|
|
||||||
|
|
||||||
const { class: className, ...props } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
}>();
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<textarea :class="cn(textarea, className)" v-bind="{ ...props }" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/textarea/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as Textarea } from "./Textarea.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import { Textarea } from "@/components/ui/textarea";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<Textarea class="w-86" placeholder="Type your message here." />
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,371 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import {
|
|
||||||
toaster,
|
|
||||||
ToasterContainer,
|
|
||||||
ToastRoot,
|
|
||||||
ToastTitle,
|
|
||||||
ToastDescription,
|
|
||||||
ToastCloseTrigger,
|
|
||||||
} from "@/components/ui/toast";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<Button @click="
|
|
||||||
() =>
|
|
||||||
toaster.create({
|
|
||||||
title: 'Event has been created',
|
|
||||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
|
||||||
type: 'info',
|
|
||||||
})
|
|
||||||
">
|
|
||||||
Show Toast
|
|
||||||
</Button>
|
|
||||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
|
||||||
<ToastRoot :key="toast.id">
|
|
||||||
<ToastTitle>{{ toast.title }}</ToastTitle>
|
|
||||||
<ToastDescription>
|
|
||||||
{{ toast.description }}
|
|
||||||
</ToastDescription>
|
|
||||||
<ToastCloseTrigger />
|
|
||||||
</ToastRoot>
|
|
||||||
</ToasterContainer>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<Button @click="
|
|
||||||
() =>
|
|
||||||
toaster.create({
|
|
||||||
title: 'Event has been created',
|
|
||||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
|
||||||
type: 'info',
|
|
||||||
})
|
|
||||||
">
|
|
||||||
Show Toast
|
|
||||||
</Button>
|
|
||||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
|
||||||
<ToastRoot :key="toast.id">
|
|
||||||
<ToastTitle>\{\{ toast.title \}\}</ToastTitle>
|
|
||||||
<ToastDescription>
|
|
||||||
\{\{ toast.description \}\}
|
|
||||||
</ToastDescription>
|
|
||||||
<ToastCloseTrigger />
|
|
||||||
</ToastRoot>
|
|
||||||
</ToasterContainer>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/toast</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/toast/toaster.ts">
|
|
||||||
{{ `
|
|
||||||
import * as toast from "@zag-js/toast";
|
|
||||||
|
|
||||||
const toaster = toast.createStore({
|
|
||||||
placement: "bottom-end",
|
|
||||||
overlap: true,
|
|
||||||
gap: 24,
|
|
||||||
});
|
|
||||||
|
|
||||||
export default toaster;
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToastRoot.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { toastRoot } from "@mykopkb/core/styles/toast.styles";
|
|
||||||
import {
|
|
||||||
boxVariants,
|
|
||||||
type BoxVariants,
|
|
||||||
} from "@mykopkb/core/styles/box.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/toast";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
raised = "single",
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
BoxVariants & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("toastApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn([boxVariants({ raised, className }), toastRoot, className])"
|
|
||||||
v-bind="{ ...api?.getRootProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<span v-bind="{ ...api?.getGhostBeforeProps() }" />
|
|
||||||
<div data-scope="toast" data-part="progressbar" />
|
|
||||||
<slot />
|
|
||||||
<span v-bind="{ ...api?.getGhostAfterProps() }" />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToastTitle.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { toastTitle } from "@mykopkb/core/styles/toast.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/toast";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("toastApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(toastTitle, className)" v-bind="{ ...api?.getTitleProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToastDescription.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { toastDescription } from "@mykopkb/core/styles/toast.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/toast";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("toastApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(toastDescription, className)" v-bind="{ ...api?.getDescriptionProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToastCloseTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { toastCloseTrigger } from "@mykopkb/core/styles/toast.styles";
|
|
||||||
import {
|
|
||||||
buttonVariants,
|
|
||||||
type ButtonVariants,
|
|
||||||
} from "@mykopkb/core/styles/button.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import { X } from "@lucide/vue";
|
|
||||||
import type { Api } from "@zag-js/toast";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
look = "outline",
|
|
||||||
variant = "secondary",
|
|
||||||
size,
|
|
||||||
...props
|
|
||||||
} = defineProps<
|
|
||||||
ButtonVariants & {
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}
|
|
||||||
>();
|
|
||||||
|
|
||||||
const api = inject<Api>("toastApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getCloseTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<Button variant="ghost" v-if="!$slots.default" :class="cn(toastCloseTrigger, className)"
|
|
||||||
v-bind="{ ...props }">
|
|
||||||
<X class="size-4" />
|
|
||||||
</Button>
|
|
||||||
<template v-else>
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<Button v-else :class="
|
|
||||||
cn(buttonVariants({ look, variant, size, className }), className)
|
|
||||||
">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
</template>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToastItem.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as toast from "@zag-js/toast";
|
|
||||||
import { provide, computed } from "vue";
|
|
||||||
import { useMachine, normalizeProps } from "@zag-js/vue";
|
|
||||||
|
|
||||||
const { toastGroup, serviceGroup, index } = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
toastGroup: toast.Options;
|
|
||||||
serviceGroup: toast.GroupService;
|
|
||||||
index: number;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const composedProps = computed(() => ({
|
|
||||||
...toastGroup,
|
|
||||||
index,
|
|
||||||
parent: serviceGroup,
|
|
||||||
}));
|
|
||||||
const service = useMachine(toast.machine, composedProps);
|
|
||||||
const api = toast.connect(service, normalizeProps);
|
|
||||||
|
|
||||||
provide("toastApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot :toast="{
|
|
||||||
...api,
|
|
||||||
id: toastGroup.id,
|
|
||||||
}" />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/ToasterContainer.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import * as toast from "@zag-js/toast";
|
|
||||||
import { toasterContainer } from "@mykopkb/core/styles/toast.styles";
|
|
||||||
import type { Store } from "@zag-js/toast";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed } from "vue";
|
|
||||||
import { ToastItem } from ".";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
toaster,
|
|
||||||
...props
|
|
||||||
} = defineProps<{ class?: string; asChild?: boolean; toaster: Store }>();
|
|
||||||
|
|
||||||
const serviceGroup = useMachine(toast.group.machine, {
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
store: toaster,
|
|
||||||
});
|
|
||||||
const apiGroup = computed(() =>
|
|
||||||
toast.group.connect(serviceGroup, normalizeProps)
|
|
||||||
);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<div :class="cn(toasterContainer, className)" v-bind="{ ...apiGroup?.getGroupProps(), ...props, ...$attrs }">
|
|
||||||
<ToastItem v-for="(toastGroup, index) in apiGroup.getToasts()" :key="toastGroup.id" :index="index"
|
|
||||||
:toastGroup="toastGroup" :serviceGroup="serviceGroup" v-slot="{ toast }">
|
|
||||||
<slot :toast="toast" />
|
|
||||||
</ToastItem>
|
|
||||||
</div>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/toast/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as toaster } from "./toaster";
|
|
||||||
export { default as ToastRoot } from "./ToastRoot.vue";
|
|
||||||
export { default as ToastTitle } from "./ToastTitle.vue";
|
|
||||||
export { default as ToastDescription } from "./ToastDescription.vue";
|
|
||||||
export { default as ToastCloseTrigger } from "./ToastCloseTrigger.vue";
|
|
||||||
export { default as ToastItem } from "./ToastItem.vue";
|
|
||||||
export { default as ToasterContainer } from "./ToasterContainer.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
toaster,
|
|
||||||
ToasterContainer,
|
|
||||||
ToastRoot,
|
|
||||||
ToastTitle,
|
|
||||||
ToastDescription,
|
|
||||||
ToastCloseTrigger,
|
|
||||||
} from "@/components/ui/toast";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{`
|
|
||||||
<Button @click="
|
|
||||||
() =>
|
|
||||||
toaster.create({
|
|
||||||
title: 'Event has been created',
|
|
||||||
description: 'Sunday, December 03, 2023 at 9:00 AM',
|
|
||||||
type: 'info',
|
|
||||||
})
|
|
||||||
">
|
|
||||||
Show Toast
|
|
||||||
</Button>
|
|
||||||
<ToasterContainer :toaster="toaster" v-slot="{ toast }">
|
|
||||||
<ToastRoot :key="toast.id">
|
|
||||||
<ToastTitle>\{\{ toast.title \}\}</ToastTitle>
|
|
||||||
<ToastDescription>
|
|
||||||
\{\{ toast.description \}\}
|
|
||||||
</ToastDescription>
|
|
||||||
<ToastCloseTrigger />
|
|
||||||
</ToastRoot>
|
|
||||||
</ToasterContainer>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -1,287 +0,0 @@
|
|||||||
<script lang="ts" setup>
|
|
||||||
import {
|
|
||||||
TooltipRoot,
|
|
||||||
TooltipTrigger,
|
|
||||||
TooltipPositioner,
|
|
||||||
TooltipContent,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
import {
|
|
||||||
Preview,
|
|
||||||
SectionTitle,
|
|
||||||
SectionContent,
|
|
||||||
InstallPackage,
|
|
||||||
PreviewCode,
|
|
||||||
} from "@/components/docs";
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<div id="preview" class="-mt-20">
|
|
||||||
<Preview>
|
|
||||||
<template #preview>
|
|
||||||
<TooltipRoot>
|
|
||||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
|
||||||
<TooltipPositioner>
|
|
||||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
|
||||||
</TooltipPositioner>
|
|
||||||
</TooltipRoot>
|
|
||||||
</template>
|
|
||||||
<template #code>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<TooltipRoot>
|
|
||||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
|
||||||
<TooltipPositioner>
|
|
||||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
|
||||||
</TooltipPositioner>
|
|
||||||
</TooltipRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</template>
|
|
||||||
</Preview>
|
|
||||||
</div>
|
|
||||||
<div id="installation">
|
|
||||||
<SectionTitle>Installation</SectionTitle>
|
|
||||||
<SectionContent>Install the following dependencies:</SectionContent>
|
|
||||||
<InstallPackage>add @zag-js/vue @zag-js/tooltip</InstallPackage>
|
|
||||||
<SectionContent>
|
|
||||||
Copy and paste the following code into your project.
|
|
||||||
</SectionContent>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipRoot.vue">
|
|
||||||
{{`
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import * as tooltip from "@zag-js/tooltip";
|
|
||||||
import type { Props } from "@zag-js/tooltip";
|
|
||||||
import { normalizeProps, useMachine } from "@zag-js/vue";
|
|
||||||
import { computed, provide } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
open = undefined,
|
|
||||||
disabled = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<Partial<Props> & { class?: string; asChild?: boolean }>();
|
|
||||||
|
|
||||||
const service = useMachine(tooltip.machine, {
|
|
||||||
...props,
|
|
||||||
positioning: {
|
|
||||||
placement: "top",
|
|
||||||
offset: { mainAxis: 10 },
|
|
||||||
},
|
|
||||||
closeDelay: 0,
|
|
||||||
openDelay: 0,
|
|
||||||
open,
|
|
||||||
disabled,
|
|
||||||
id: crypto.randomUUID(),
|
|
||||||
});
|
|
||||||
const api = computed(() => tooltip.connect(service, normalizeProps));
|
|
||||||
|
|
||||||
provide("tooltipApi", api);
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<slot />
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipTrigger.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tooltipTrigger } from "@mykopkb/core/styles/tooltip.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { Button } from "@/components/ui/button";
|
|
||||||
import type { Api } from "@zag-js/tooltip";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tooltipApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot v-bind="{ ...api?.getTriggerProps(), ...props, ...$attrs }">
|
|
||||||
<Button variant="secondary" look="outline" v-if="!asChild" :class="cn(tooltipTrigger, className)">
|
|
||||||
<slot />
|
|
||||||
</Button>
|
|
||||||
<slot v-else />
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipPositioner.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tooltipPositioner } from "@mykopkb/core/styles/tooltip.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/tooltip";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tooltipApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Teleport to="body">
|
|
||||||
<Slot :class="cn(tooltipPositioner, className)"
|
|
||||||
v-bind="{ ...api?.getPositionerProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</Teleport>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipContent.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tooltipContent } from "@mykopkb/core/styles/tooltip.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import { TooltipArrow, TooltipArrowTip } from ".";
|
|
||||||
import type { Api } from "@zag-js/tooltip";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tooltipApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tooltipContent, className)" v-bind="{ ...api?.getContentProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
<TooltipArrow>
|
|
||||||
<TooltipArrowTip />
|
|
||||||
</TooltipArrow>
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipArrow.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tooltipArrow } from "@mykopkb/core/styles/tooltip.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/tooltip";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tooltipApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tooltipArrow, className)" v-bind="{ ...api?.getArrowProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/TooltipArrowTip.vue">
|
|
||||||
{{ `
|
|
||||||
<script lang="ts" setup>
|
|
||||||
import { cn } from "@mykopkb/core/utils/cn";
|
|
||||||
import { tooltipArrowTip } from "@mykopkb/core/styles/tooltip.styles";
|
|
||||||
import { Slot } from "@/components/ui/slot";
|
|
||||||
import type { Api } from "@zag-js/tooltip";
|
|
||||||
import { inject } from "vue";
|
|
||||||
|
|
||||||
const {
|
|
||||||
class: className,
|
|
||||||
asChild = false,
|
|
||||||
...props
|
|
||||||
} = defineProps<{
|
|
||||||
class?: string;
|
|
||||||
asChild?: boolean;
|
|
||||||
}>();
|
|
||||||
|
|
||||||
const api = inject<Api>("tooltipApi");
|
|
||||||
</script>
|
|
||||||
|
|
||||||
<template>
|
|
||||||
<Slot :class="cn(tooltipArrowTip, className)" v-bind="{ ...api?.getArrowTipProps(), ...props, ...$attrs }">
|
|
||||||
<slot v-if="asChild" />
|
|
||||||
<div v-else>
|
|
||||||
<slot />
|
|
||||||
</div>
|
|
||||||
</Slot>
|
|
||||||
</template>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode title="components/ui/tooltip/index.ts">
|
|
||||||
{{ `
|
|
||||||
export { default as TooltipRoot } from "./TooltipRoot.vue";
|
|
||||||
export { default as TooltipTrigger } from "./TooltipTrigger.vue";
|
|
||||||
export { default as TooltipPositioner } from "./TooltipPositioner.vue";
|
|
||||||
export { default as TooltipContent } from "./TooltipContent.vue";
|
|
||||||
export { default as TooltipArrow } from "./TooltipArrow.vue";
|
|
||||||
export { default as TooltipArrowTip } from "./TooltipArrowTip.vue";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<SectionContent>
|
|
||||||
Update the import paths to match your project setup.
|
|
||||||
</SectionContent>
|
|
||||||
</div>
|
|
||||||
<div id="usage">
|
|
||||||
<SectionTitle>Usage</SectionTitle>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
import {
|
|
||||||
TooltipRoot,
|
|
||||||
TooltipTrigger,
|
|
||||||
TooltipPositioner,
|
|
||||||
TooltipContent,
|
|
||||||
} from "@/components/ui/tooltip";
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
<PreviewCode>
|
|
||||||
{{ `
|
|
||||||
<TooltipRoot>
|
|
||||||
<TooltipTrigger>Hover Me</TooltipTrigger>
|
|
||||||
<TooltipPositioner>
|
|
||||||
<TooltipContent>I am a tooltip!</TooltipContent>
|
|
||||||
</TooltipPositioner>
|
|
||||||
</TooltipRoot>
|
|
||||||
` }}
|
|
||||||
</PreviewCode>
|
|
||||||
</div>
|
|
||||||
</template>
|
|
||||||
@@ -2,6 +2,7 @@ import "./index.css";
|
|||||||
|
|
||||||
import { createApp } from 'vue'
|
import { createApp } from 'vue'
|
||||||
import { pinia } from '@/pinia'
|
import { pinia } from '@/pinia'
|
||||||
|
import { applyAppearance } from '@/utils/applyAppearance'
|
||||||
|
|
||||||
import App from './App.vue'
|
import App from './App.vue'
|
||||||
import router from './router/index.ts'
|
import router from './router/index.ts'
|
||||||
@@ -9,6 +10,7 @@ import router from './router/index.ts'
|
|||||||
const app = createApp(App)
|
const app = createApp(App)
|
||||||
|
|
||||||
app.use(pinia)
|
app.use(pinia)
|
||||||
|
applyAppearance()
|
||||||
app.use(router)
|
app.use(router)
|
||||||
|
|
||||||
app.mount('#app')
|
app.mount('#app')
|
||||||
|
|||||||
@@ -1,483 +1,25 @@
|
|||||||
import type { Menu } from '@/core/types/menu'
|
import type { Menu } from '@/core/types/menu'
|
||||||
import { authMenu } from '@/modules/auth'
|
|
||||||
import { userMenu } from '@/modules/user'
|
import { userMenu } from '@/modules/user'
|
||||||
import { roleMenu } from '@/modules/role'
|
import { roleMenu } from '@/modules/role'
|
||||||
import { membershipApplicationMenu } from '@/modules/membership-application'
|
import { membershipApplicationMenu } from '@/modules/membership-application'
|
||||||
import { activityMenu } from '@/modules/activity'
|
import { activityMenu } from '@/modules/activity'
|
||||||
|
import { dashboardMenu } from '@/modules/dashboard/menu'
|
||||||
|
import { externalSystemMenu } from '@/modules/external-system/menu'
|
||||||
|
import { activityLogMenu } from '@/modules/activity-log/menu'
|
||||||
|
|
||||||
export type { Menu }
|
export type { Menu }
|
||||||
|
|
||||||
const mainMenu: (string | Menu)[] = [
|
const mainMenu: (string | Menu)[] = [
|
||||||
'Umum',
|
'Umum',
|
||||||
|
...dashboardMenu,
|
||||||
|
...externalSystemMenu,
|
||||||
...activityMenu,
|
...activityMenu,
|
||||||
'Teknologi Maklumat',
|
'Teknologi Maklumat',
|
||||||
...roleMenu,
|
...roleMenu,
|
||||||
|
...activityLogMenu,
|
||||||
'Pentadbiran',
|
'Pentadbiran',
|
||||||
...membershipApplicationMenu,
|
...membershipApplicationMenu,
|
||||||
...userMenu,
|
...userMenu,
|
||||||
'GENERAL REPORTS',
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Dashboards',
|
|
||||||
badge: 4,
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'PanelBottomClose',
|
|
||||||
route_name: 'dashboard-overview-1',
|
|
||||||
title: 'Overview 1',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'SquareKanban',
|
|
||||||
title: 'E-Commerce',
|
|
||||||
badge: 2,
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'categories',
|
|
||||||
title: 'Categories',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'add-product',
|
|
||||||
title: 'Add Product',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Products',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'product-list',
|
|
||||||
title: 'Product List',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'product-grid',
|
|
||||||
title: 'Product Grid',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Transactions',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'transaction-list',
|
|
||||||
title: 'Transaction List',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'transaction-detail',
|
|
||||||
title: 'Transaction Detail',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Sellers',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'seller-list',
|
|
||||||
title: 'Seller List',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'seller-detail',
|
|
||||||
title: 'Seller Detail',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'reviews',
|
|
||||||
title: 'Reviews',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'APPS',
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'inbox',
|
|
||||||
title: 'Inbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'file-manager',
|
|
||||||
title: 'File Manager',
|
|
||||||
badge: 5,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'point-of-sale',
|
|
||||||
title: 'Point of Sale',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'chat',
|
|
||||||
title: 'Chat',
|
|
||||||
badge: 3,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'post',
|
|
||||||
title: 'Post',
|
|
||||||
},
|
|
||||||
// {
|
|
||||||
// icon: 'CircleGauge',
|
|
||||||
// route_name: 'calendar',
|
|
||||||
// title: 'Calendar',
|
|
||||||
// },
|
|
||||||
'PAGES',
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Crud',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'crud-data-list',
|
|
||||||
title: 'Data List',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Users',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'users-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'users-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'users-layout-3',
|
|
||||||
title: 'Layout 3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Pages',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Wizards',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'wizard-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'wizard-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'wizard-layout-3',
|
|
||||||
title: 'Layout 3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Blog',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'blog-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'blog-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'blog-layout-3',
|
|
||||||
title: 'Layout 3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Pricing',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'pricing-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'pricing-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Invoice',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'invoice-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'invoice-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'FAQ',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'faq-layout-1',
|
|
||||||
title: 'Layout 1',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'faq-layout-2',
|
|
||||||
title: 'Layout 2',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'faq-layout-3',
|
|
||||||
title: 'Layout 3',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
'UI COMPONENTS',
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Base',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'slot',
|
|
||||||
title: 'Slot',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'box',
|
|
||||||
title: 'Box',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'scroll-area',
|
|
||||||
title: 'Scroll Area',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Navigation',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'breadcrumb',
|
|
||||||
title: 'Breadcrumb',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'menu',
|
|
||||||
title: 'Menu',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'pagination',
|
|
||||||
title: 'Pagination',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'tabs',
|
|
||||||
title: 'Tabs',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Forms',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'button',
|
|
||||||
title: 'Button',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'checkbox',
|
|
||||||
title: 'Checkbox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'combobox',
|
|
||||||
title: 'Combobox',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'datepicker',
|
|
||||||
title: 'Datepicker',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'field',
|
|
||||||
title: 'Field',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'input',
|
|
||||||
title: 'Input',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'native-select',
|
|
||||||
title: 'Native Select',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'radio-group',
|
|
||||||
title: 'Radio Group',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'select',
|
|
||||||
title: 'Select',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'slider',
|
|
||||||
title: 'Slider',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'switch',
|
|
||||||
title: 'Switch',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'textarea',
|
|
||||||
title: 'Textarea',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Data Display',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'accordion',
|
|
||||||
title: 'Accordion',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'avatar',
|
|
||||||
title: 'Avatar',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'badge',
|
|
||||||
title: 'Badge',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'carousel',
|
|
||||||
title: 'Carousel',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'table',
|
|
||||||
title: 'Table',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Feedback',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'alert',
|
|
||||||
title: 'Alert',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'progress-circular',
|
|
||||||
title: 'Progress Circular',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'progress-linear',
|
|
||||||
title: 'Progress Linear',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'toast',
|
|
||||||
title: 'Toast',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Overlay',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'dialog',
|
|
||||||
title: 'Dialog',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'popover',
|
|
||||||
title: 'Popover',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'sheet',
|
|
||||||
title: 'Sheet',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'tooltip',
|
|
||||||
title: 'Tooltip',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
title: 'Visuals',
|
|
||||||
sub_menu: [
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'chart',
|
|
||||||
title: 'Chart',
|
|
||||||
},
|
|
||||||
{
|
|
||||||
icon: 'CircleGauge',
|
|
||||||
route_name: 'map',
|
|
||||||
title: 'Map',
|
|
||||||
},
|
|
||||||
],
|
|
||||||
},
|
|
||||||
]
|
]
|
||||||
|
|
||||||
export default mainMenu
|
export default mainMenu
|
||||||
|
|||||||
@@ -0,0 +1,74 @@
|
|||||||
|
import { onMounted, ref, watch } from 'vue'
|
||||||
|
import debounce from 'lodash/debounce'
|
||||||
|
import { useApiPagination } from '@/composables/useApiPagination'
|
||||||
|
import { getApiErrorMessage } from '@/core/utils/getApiErrorMessage'
|
||||||
|
import { listActivityLogs } from '../services/activity-log.service'
|
||||||
|
import type { ActivityLogItem } from '../types/activity-log.types'
|
||||||
|
|
||||||
|
export function useActivityLogList() {
|
||||||
|
const activityLogs = ref<ActivityLogItem[]>([])
|
||||||
|
const loading = ref(false)
|
||||||
|
const error = ref<string | null>(null)
|
||||||
|
const search = ref('')
|
||||||
|
const page = ref(1)
|
||||||
|
const itemsPerPage = ref(10)
|
||||||
|
|
||||||
|
const { pagination, applyPagination } = useApiPagination({ per_page: 10 })
|
||||||
|
|
||||||
|
async function fetchActivityLogs(requestPage = page.value) {
|
||||||
|
loading.value = true
|
||||||
|
error.value = null
|
||||||
|
|
||||||
|
try {
|
||||||
|
const data = await listActivityLogs({
|
||||||
|
page: requestPage,
|
||||||
|
per_page: itemsPerPage.value,
|
||||||
|
search: search.value.trim() || undefined,
|
||||||
|
})
|
||||||
|
|
||||||
|
activityLogs.value = data.data
|
||||||
|
applyPagination(data.pagination)
|
||||||
|
page.value = data.pagination.current_page
|
||||||
|
} catch (err) {
|
||||||
|
error.value = getApiErrorMessage(err, 'Gagal memuatkan log aktiviti.')
|
||||||
|
activityLogs.value = []
|
||||||
|
} finally {
|
||||||
|
loading.value = false
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const debouncedSearch = debounce(() => {
|
||||||
|
fetchActivityLogs(1)
|
||||||
|
}, 400)
|
||||||
|
|
||||||
|
watch(search, () => {
|
||||||
|
debouncedSearch()
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(page, (nextPage, previousPage) => {
|
||||||
|
if (nextPage !== previousPage) {
|
||||||
|
fetchActivityLogs(nextPage)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
watch(itemsPerPage, (nextValue, previousValue) => {
|
||||||
|
if (nextValue !== previousValue) {
|
||||||
|
fetchActivityLogs(1)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
onMounted(() => {
|
||||||
|
fetchActivityLogs(1)
|
||||||
|
})
|
||||||
|
|
||||||
|
return {
|
||||||
|
activityLogs,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
search,
|
||||||
|
page,
|
||||||
|
itemsPerPage,
|
||||||
|
pagination,
|
||||||
|
fetchActivityLogs,
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
export { activityLogLayoutRoutes } from './routes'
|
||||||
|
export { activityLogMenu } from './menu'
|
||||||
@@ -0,0 +1,10 @@
|
|||||||
|
import type { Menu } from '@/core/types/menu'
|
||||||
|
|
||||||
|
export const activityLogMenu: Menu[] = [
|
||||||
|
{
|
||||||
|
icon: 'ScrollText',
|
||||||
|
route_name: 'list-activity-logs',
|
||||||
|
title: 'Log Aktiviti',
|
||||||
|
permission: 'lihat log aktiviti',
|
||||||
|
},
|
||||||
|
]
|
||||||
@@ -0,0 +1,105 @@
|
|||||||
|
<script lang="ts" setup>
|
||||||
|
import dayjs from 'dayjs'
|
||||||
|
import { Search } from '@lucide/vue'
|
||||||
|
import { AlertRoot, AlertTitle, AlertDescription } from '@/components/ui/alert'
|
||||||
|
import { Input } from '@/components/ui/input'
|
||||||
|
import DataTable from '@/components/ui/usage/DataTable.vue'
|
||||||
|
import type { TableHeader } from '@/components/ui/usage/DataTable.vue'
|
||||||
|
import { useActivityLogList } from '../composables/useActivityLogList'
|
||||||
|
import { formatModelType } from '../utils/activity-log.utils'
|
||||||
|
|
||||||
|
const {
|
||||||
|
activityLogs,
|
||||||
|
loading,
|
||||||
|
error,
|
||||||
|
search,
|
||||||
|
page,
|
||||||
|
itemsPerPage,
|
||||||
|
pagination,
|
||||||
|
} = useActivityLogList()
|
||||||
|
|
||||||
|
function formatDateTime(value: string | null | undefined): string {
|
||||||
|
if (!value) return '-'
|
||||||
|
return dayjs(value).format('DD MMM YYYY, HH:mm')
|
||||||
|
}
|
||||||
|
|
||||||
|
function formatCauserName(item: { causer?: { name?: string } | null }): string {
|
||||||
|
return item.causer?.name ?? '-'
|
||||||
|
}
|
||||||
|
|
||||||
|
const headers: TableHeader[] = [
|
||||||
|
{ title: 'Bil.', key: '#', sortable: false },
|
||||||
|
{
|
||||||
|
title: 'Tarikh',
|
||||||
|
key: 'created_at',
|
||||||
|
sortable: false,
|
||||||
|
exportValue: (item) => formatDateTime(item.created_at),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Pengguna',
|
||||||
|
key: 'causer.name',
|
||||||
|
sortable: false,
|
||||||
|
exportValue: (item) => formatCauserName(item),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
title: 'Emel',
|
||||||
|
key: 'causer.email',
|
||||||
|
sortable: false,
|
||||||
|
exportValue: (item) => item.causer?.email ?? '-',
|
||||||
|
},
|
||||||
|
{ title: 'Keterangan', key: 'description', sortable: false },
|
||||||
|
{
|
||||||
|
title: 'Subjek',
|
||||||
|
key: 'subject_type',
|
||||||
|
sortable: false,
|
||||||
|
exportValue: (item) => formatModelType(item.subject_type),
|
||||||
|
},
|
||||||
|
{ title: 'Peristiwa', key: 'event', sortable: false },
|
||||||
|
]
|
||||||
|
</script>
|
||||||
|
|
||||||
|
<template>
|
||||||
|
<div class="w-full space-y-6">
|
||||||
|
<div>
|
||||||
|
<h2 class="text-lg font-medium">Log Aktiviti</h2>
|
||||||
|
<p class="mt-1 text-sm opacity-70">Semak rekod aktiviti pengguna dalam sistem.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<AlertRoot v-if="error" variant="danger">
|
||||||
|
<AlertTitle>Error</AlertTitle>
|
||||||
|
<AlertDescription>{{ error }}</AlertDescription>
|
||||||
|
</AlertRoot>
|
||||||
|
|
||||||
|
<DataTable :headers="headers" :items="activityLogs" :loading="loading" :pagination="pagination" show-pagination
|
||||||
|
exportable export-file-name="activity-logs" v-model:page="page" v-model:items-per-page="itemsPerPage">
|
||||||
|
<template #toolbar>
|
||||||
|
<div class="relative w-full max-w-md flex-1">
|
||||||
|
<Search class="pointer-events-none absolute top-1/2 left-3 z-10 size-4 -translate-y-1/2 text-foreground/50"
|
||||||
|
aria-hidden="true" />
|
||||||
|
<Input v-model="search" type="search" placeholder="Cari keterangan, subjek, peristiwa, pengguna..."
|
||||||
|
class="w-full pl-9" aria-label="Cari log aktiviti" />
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item.created_at="{ item }">
|
||||||
|
{{ formatDateTime(item.created_at) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item.causer.name="{ item }">
|
||||||
|
{{ formatCauserName(item) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item.causer.email="{ item }">
|
||||||
|
<span class="lowercase">{{ item.causer?.email ?? '-' }}</span>
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item.subject_type="{ item }">
|
||||||
|
{{ formatModelType(item.subject_type) }}
|
||||||
|
</template>
|
||||||
|
|
||||||
|
<template #item.event="{ item }">
|
||||||
|
{{ item.event ?? '-' }}
|
||||||
|
</template>
|
||||||
|
</DataTable>
|
||||||
|
</div>
|
||||||
|
</template>
|
||||||