Giter Club home page Giter Club logo

spartan's Introduction

spartan

A spartan shield

Discord server Twitter

Welcome to the spartan mono-repo. This Nx repository holds both the spartan/stack and spartan/ui libraries.

Important: This is a work in progress, and we update the README as major development efforts are started.

The 300 spartans

All of spartan is an MIT-licensed open source project with its ongoing development made possible by contributors and sponsors.

Our initial 300 contributors and sponsors are featured here and on the front page of spartan.ng

  1. goetzrobin
  2. elite-benni
  3. thatsamsonkid
  4. mihajm
  5. ajitzero
  6. arturgawlik
  7. deepakrudrapaul
  8. evanfuture
  9. AdditionAddict
  10. Altamimi-Dev
  11. ferat
  12. jeremy-js-devweb
  13. heddendorp
  14. tutkli
  15. Pascalmh
  16. okkindel
  17. marcjulian
  18. oidre
  19. nartc
  20. santoshyadavdev
  21. markostanimirovic
  22. theo-matzavinos
  23. jkuri
  24. dongphuong0905
  25. DominikPieper
  26. brandonroberts
  27. izikd-
  28. ryancraigmartin
  29. gaetanBloch
  30. gergobergo
  31. rpacheco124
  32. benjaminforras
  33. jstnjs
  34. r3ps4J
  35. Celtian
  36. miljan-code
  37. alexciesielski
  38. ty-ler
  39. MatznRisto
  40. badsgahhl
  41. monacodelisa
  42. tomdev9
  43. ragul1697
  44. snydertechnologies

Become a spartan today!

spartan/ui

spartan/ui is our effort to port the incredible shadcn/ui project over to the Angular ecosystem.

The idea is to create un-styled primitives similar to Radix with the help of the Angular CDK and other proven community solutions And then add the beautiful shadcn styles with primitives (and components where necessary).

You can find all UI primitives in the libs/ui folder.

Each primitive is made up off an un-styled brain library, which provides all functionality and a helm library, which adds the styles.

There's also a libs/cli folder, which contains the Nx-plugin & Angular CLI code that allows users to add spartan/ui to their Nx or Angular workspace in a simple way.

Install Dependencies

Run pnpm install to install the dependencies of this project.

Development with storybook

A storybook project is set up and is the primary way to develop UI components. You can run it with:

pnpm storybook

At the root of each primitive's folder, e.g. libs/ui/accordion you will find a stories file, e.g, accordion.stories.ts.

Use these files to add stories and drive development of the primitives.

Testing

spartan uses Jest for tests. To test all projects locally, run the following command from the root folder:

pnpm test

e2e testing

Cypress e2e testing is set up to run on the storybook. You can run it with:

pnpm e2e

To add your own e2e tests add them to the apps/ui-storybook-e2e application.

Progress (37/43)

We finished porting over 37/43 UI primitives. See a more detailed breakdown here!

spartan/stack

An example application running on Supabase, Drizzle, Analog, tRPC, Tailwind, Angular, and Nx. It also serves as the documentation page introducing the stack and UI library.

Follow the directions in the official documentation to set up your own project: https://www.spartan.ng/stack/overview

Example App

In the apps folder of this repository, you can also find an example application of the spartan stack. It also serves as the documentation page for this project.

For now. The goal is to move the docs to Astro.

Follow the directions below to get it up and running:

Prerequisites

  • You will need pnpm (or a different package manager) installed.
  • You will need to set up a Supabase account (it's free)
  • You will need NodeJs installed. The version I have working is 18.13.0.

Development server

Then you can run the following command:

pnpm nx serve app

or

pnpm dev

for a dev server. Navigate to http://localhost:4200/. The app will automatically reload if you change any of the source files.

Database

We use Drizzle to connect to a Supabase instance for the example app.

Add an .env file to your repo with the following contents:

Add a .env file at the root of your Nx workspace and add the connection string like so:

DATABASE_URL="postgresql://postgres:[YOUR-PASSWORD]@db.[YOUR-SUPABASE-REFERENCE-ID].supabase.co:5432/postgres?schema=public"

And make sure to run the following script in your Supabase editor to set up the necessary tables:

create table
  public.note (
    id bigserial,
    title text not null,
    content text null,
    created_at timestamp with time zone null default current_timestamp,
    constraint notes_pkey primary key (id)
  ) tablespace pg_default;

[!NOTE] > .env should be added to .gitignore

Understand this workspace

Run pnpm nx graph to see a diagram of the dependencies of the projects.

Further help

Reach out to me on Twitter or GitHub if you run into any issues.

spartan's People

Contributors

ajitzero avatar alexciesielski avatar badsgahhl avatar benjaminforras avatar benpsnyder avatar brandonroberts avatar celtian avatar deepakrudrapaul avatar devwedeloper avatar dominikpieper avatar elite-benni avatar evanfuture avatar gergobergo avatar goetzrobin avatar jkuri avatar jstnjs avatar kkamman avatar marcjulian avatar matznristo avatar mihajm avatar monacodelisa avatar nartc avatar oidre avatar pascalmh avatar r3ps4j avatar ragul1697 avatar thatsamsonkid avatar theo-matzavinos avatar tomdev9 avatar tutkli avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

spartan's Issues

RFC: Checkbox

Which scope/s are relevant/related to the feature request?

checkbox

Information

Checkbox

Assesment

Time Complexity Risk Description
3 2 0 Mostly styles

Dependencies

None

brn


API (proposed)

The brnCheckbox directive would set the inputs type to checkbox & control the aria-checked attribute based on the inputs value & indeterminate state.
Toggling the indeterminate state would not change the inputs value, just the aria-checked attribute.
The directive also adds a readonly input that intercepts the click & change events

<input name="test" brnCheckbox /><label for="test">Test</label>

Alternatively it could apply only to inputs of type checkbox, but that seems like a bit of repetition.

<input name="test" type="checkbox" brnCheckbox /><label for="test">Test</label>

Another alternative would be to create a checkbox component that handles the label, this would also allow us to bind aria-labeledBy to the inputs id & toggle the checkbox when the user clicks the label. This would however require us to either create a brnLabel directive or use something else for the checkbox, & does not sync with the current implementations of hlmInput & hlmLabel.

<brn-checkbox name="test">Test</brn-checkbox>

Proposed inputs

Input Type Default Description
readonly BooleanInput false toggles the readonly state of the checkbox

hlm


The hlmCheckbox directive would be composed with the brnCheckbox & apply the appropriate styles.

Aria


Roles, States & Properties

aria-checked should be true or false based on the inputs value or mixed if indeterminate
when focused space should be able to toggle the checkbox (this is a html default, so it only applies if we go the custom component route)

checkbox

Radix


API

const CheckboxDemo = () => (
      <Checkbox.Root
        defaultChecked
        id="c1"
      >
        <Checkbox.Indicator>
          <CheckIcon />
        </Checkbox.Indicator>
      </Checkbox.Root>
      <label htmlFor="c1">
        Accept terms and conditions.
      </label>
);

Source

https://www.radix-ui.com/docs/primitives/components/checkbox

shadcn


API

export function CheckboxWithText() {
  return (
      <Checkbox id="terms2" />
      <label
        htmlFor="terms2"
        className="text-sm font-medium leading-none peer-disabled:cursor-not-allowed peer-disabled:opacity-70"
      >
        Accept terms and conditions
      </label>
  )
}

Source

https://ui.shadcn.com/docs/components/checkbox

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Configure prettier as a pre-commit hook & GitHub Action

While working on #16 I noticed that 86 files have changed after running Prettier, most of them untouched by me. Looks like prettier was not run explicitly in some commits.

I recommend 2 things:

  1. Set up husky to trigger npm run prettify as a pre-commit hook locally. - This is straightforward.
  2. Set up a GitHub Action to format the project after merging any MR to the main branch. This is useful for people directly editing just the docs via the GitHub website. - I can't find a reliable reference right now, but I have seen this being done in other projects with an extra commit by github-actions-bot to include Prettier changes.

Examples of how to wrap Brain components

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

Background

One of the things that really excites me about this project is the potential to use the brain library as internal implementation details of our own, custom UI library.

In order to do this, we'd create wrappers around the brain components (akin to helm) that provide the styles and configuration.

I notice that the helm examples require the consumer of the components to be aware of both helm and brain (by adding both directives). This composable pattern is great for flexibility, but isn't something we'd want to expose to the consumers. The reason being is that, to get a specific type of menu, the consumer has to add lots of directives, classes, styles, which should be refactored into a higher-level component that handles this consistently.

So, we'd like all of the brain stuff to be fully encapsulated behind our own component abstractions.

The ability to refactor into wrapper components is an important principle for any component library that will be used to support a large application.

Prior art

The React Radix project has some really great concepts that facilitate this kind of approach. Namely, the asChild feature, which allows component developers to control the rendered elements.

Angular Solutions

Create custom components and use brain directives internally

We create our own set of components and use brain as an internal implementation detail.

E.g. Our Menu component would add the BrnMenu directive using directive composition.

This is great in theory as the use of brain is hidden behind our component API.

Unfortunately, angular has long removed the replace:true feature of AngularJS so we have to deal with the host element wrapping our custom markup in a way that often interferes with brain's (or other third-party library's) requirement to have a direct parent/child relationship, or for the brain directives to be added to the element

E.g.

<app-menu> <!-- this has `BrnMenu` in hostDirectives -->
   <app-menu-item title="Item 1" /> <!-- this has `BrnMenuItem` in hostDirectives -->
</app-menu>

Becomes

<app-menu>
   <app-menu-item title="Item 1">
     <button /> <!-- This is now a child of the element that the brnMenuItem directive is added to -->
   </app-menu-item>
</app-menu>
  • If we leave the brnMenuItem directive on the host element, it won't apply the a11y logic to the right element (the button)
  • If we moved the brnMenuItem directive to the button instead, it would no longer be a direct child of brnMenu and wouldn't work.

Directive Composition

If we're prepared to not completely encapsulate everything behind our component API, we could make use of custom directive wrappers like:

<app-menu>
   <button *appMenuItem title="">
</app-menu>

Where both Menu and MenuItem are custom directives that use hostBindings to compose the brain directives.

This is the same method brain uses to wrap Cdk. Where brain re-exports the cdk inputs/outputs using the directive composition API.

Unfortunately, our custom wrappers won't allow us to export those inputs/outputs again, because they don't belong to the brain components (they belong to cdk).

This appears to be a concious decision by Angular, so, unless this changes, I'm not sure there's much we can do here.

Some variant of the asChild feature of Radix

Have the brain directives support an asChild option that, when configured, makes the directive apply its logic to a child element instead of the host.

I would imagine this would add significant complexity to brain, and the React solutions to this benefit from things like prop spreading.

Closing

Really just looking for some discussion on how we see brain being used by component library developers, instead of just how you might use it to build a business requirement directly.

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Accessing brain state inside wrapper directives/components

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

We maintain our own component library and love the concept of brain!

Ideally, we'll use brain directives/components to implement some of our components, while keeping brain encapsulated behind our component's API.

As an example, we've created a MenuItemCheckbox component that adds the BrnMenuItemCheckboxDirective as a hostDirective.

This works great, brain is handling the logic and exposing this via element states and classes so we can provide our own markup and style this appropriately.

However, we already have our own set of UI components (e.g. a checkbox) that we'd like to use inside of a MenuItemCheckbox component.

I notice in the Helm version of MenuItemCheckbox, you're hiding the checkbox with opacity:0 and showing it based on the checked class added by brain.

Instead, I'd like to be able to access the "checked" state from BrnMenuItemCheckboxDirective and pass that through to my child component.

I notice you're passing around state (dialogs) using signals in other places. Maybe there's a good reason not to do it elsewhere?

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Missing exportAs inside brnTabs

Please provide the environment you discovered this bug in.

Missing exportAs inside brnTabs

Which area/package is the issue in?

tabs

Description

The issue occurs when I try to create a custom component and trying to access brnTabs value.

image

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Calendar

Which scope/s are relevant/related to the feature request?

calendar

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Icon does not work in OnPush Components

Please provide the environment you discovered this bug in.

"@angular/cdk": "^17.0.0",
"@angular/core": "~17.0.2",
"@spartan-ng/ui-core": "^0.0.1-alpha.309",

Which area/package is the issue in?

Icon

Description

When using OnPush on the Parent Component the size is not working, because the Hostbinding is not refreshed.

Please provide the exception or error you saw

The Size of the icon is always the default size.

Other information

On Angular 16 setting the HostBinding in an effect worked, but does not work on Angular 17 anymore.
Here is a Stackblitz that shows, that on Angular 17 setting the HostBinding in an effect does not work.
It also shows some concepts, how it could be done.
https://stackblitz.com/edit/stackblitz-starters-hzfv9f?file=src%2Fdefault-parent.component.ts

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Scroll Area width

Please provide the environment you discovered this bug in.

There is an issue with import {NgScrollbarModule} from 'ngx-scrollbar';

Which area/package is the issue in?

scroll-area

Description

on my project I have exactly the same problem with the width.
The case is to switch between code and preview, the result is:

image

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Wrong alert Icon placement in Firefox

Please provide the environment you discovered this bug in.

Open Alert with Firefox 116.0.3 with layout.css.has-selector.enabled disabled, which is the default currently.

When you set layout.css.has-selector.enabled to enabled it works.

Which area/package is the issue in?

alert

Description

The alert icon is overlapping with the content in Firefox. This is because [&:has([hlmAlertIcon])]:pl-11 is not applied, because :has is not enabled by default in FIrefox.

CleanShot 2023-08-24 at 12 25 45

CleanShot 2023-08-24 at 12 23 43

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Toast

Which scope/s are relevant/related to the feature request?

toast

Information

Current frontrunner is wrapping this great library: https://github.com/ngneat/hot-toast instead of implementing ourselves. Wondering if there are enough options to style through tw classes...

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Dialog event (stateChanged) not emit values

Please provide the environment you discovered this bug in.

Angular CLI: 17.0.0
Node: 21.4.0
Package Manager: npm 10.2.4
OS: win32 x64

{
  .
  .
  .
  .
  "dependencies": {
    "@angular/animations": "^17.0.0",
    "@angular/cdk": "17.0.0",
    "@angular/common": "^17.0.0",
    "@angular/compiler": "^17.0.0",
    "@angular/core": "^17.0.0",
    "@angular/forms": "^17.0.0",
    "@angular/platform-browser": "^17.0.0",
    "@angular/platform-browser-dynamic": "^17.0.0",
    "@angular/router": "^17.0.0",
    "@ng-icons/core": "^25.1.0",
    "@ng-icons/radix-icons": "^26.3.0",
    "@ngneat/cmdk": "^2.0.0",
    "@spartan-ng/ui-command-brain": "0.0.1-alpha.319",
    "@spartan-ng/ui-core": "^0.0.1-alpha.319",
    "@spartan-ng/ui-dialog-brain": "0.0.1-alpha.319",
    "@spartan-ng/ui-popover-brain": "0.0.1-alpha.319",
    "class-variance-authority": "^0.6.0",
    "clsx": "^1.2.1",
    "rxjs": "~7.8.0",
    "tslib": "^2.3.0",
    "zone.js": "~0.14.2"
  },
  "devDependencies": {
    "@angular-devkit/build-angular": "^17.0.7",
    "@angular/cli": "^17.0.7",
    "@angular/compiler-cli": "^17.0.0",
    "@spartan-ng/cli": "^0.0.1-alpha.331",
    "@types/jasmine": "~5.1.0",
    "autoprefixer": "^10.4.16",
    "jasmine-core": "~5.1.0",
    "karma": "~6.4.0",
    "karma-chrome-launcher": "~3.2.0",
    "karma-coverage": "~2.2.0",
    "karma-jasmine": "~5.1.0",
    "karma-jasmine-html-reporter": "~2.1.0",
    "postcss": "^8.4.32",
    "tailwind-merge": "^1.14.0",
    "tailwindcss": "^3.4.0",
    "tailwindcss-animate": "^1.0.6",
    "typescript": "~5.2.2"
  }
}

Which area/package is the issue in?

command and popover

Description

I am trying to replicate the code published at spartan-ui/combobox in a new project. These are the steps I followed:

ng new spartan-ui-combobox-example

// Install Tailwind 

// Install Spartan UI

npm i -D @spartan-ng/cli

npm i @spartan-ng/ui-core

ng g @spartan-ng/cli:ui-theme

// Now I follow the steps to install the primitives needed for the combobox
ng g @spartan-ng/cli:ui command

ng g @spartan-ng/cli:ui popover

NOTE HERE -> the command to generate the command primitive does not install the "@ngneat/cmdk" dependency, and the "@ng-icons/radix-icons" dependency is also not installed. I had to manually install these dependencies.

I copy and paste the example code for the combobox, and everything seems to work fine. However, when I click on some of the combobox options, it does not close. To close it, I have to click outside the combobox.

spartan-ui-bug-dialog

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Doc on loading button

Please provide the environment you discovered this bug in.

In prod on https://www.spartan.ng/components/button loading section.

Which area/package is the issue in?

button

Description

you can change the doc

from

import { Component } from '@angular/core';
import { HlmButtonDirective } from '@spartan-ng/ui-button-helm';
import { HlmSpinnerComponent } from '@spartan-ng/ui-spinner-helm';
import { HlmIconComponent } from '@spartan-ng/ui-icon-helm';
import { provideIcons } from '@ng-icons/core';
import { radixSymbol } from '@ng-icons/radix-icons';

@Component({
  selector: 'spartan-button-icon',
  standalone: true,
  imports: [HlmButtonDirective],
  template: `
    <button hlmBtn variant='icon'>Icon</button> `,})
export class ButtonIconComponent {}

to

import { Component } from '@angular/core';
import { HlmButtonDirective } from '@spartan-ng/ui-button-helm';
import { HlmSpinnerComponent } from '@spartan-ng/ui-spinner-helm';
import { HlmIconComponent } from '@spartan-ng/ui-icon-helm';
import { provideIcons } from '@ng-icons/core';
import { radixSymbol } from '@ng-icons/radix-icons';

@Component({
  selector: 'spartan-button-loading',
  standalone: true,
  imports: [HlmButtonDirective, HlmSpinnerComponent, HlmIconComponent],
  providers: [provideIcons({ radixSymbol })],
  template: `
    <button disabled hlmBtn><hlm-icon name="radixSymbol" size="sm" class="mr-2 animate-spin" /> Please wait</button>
  `,
})
export class ButtonLoadingComponent {}

and maybe the HlmSpinnerComponent is useless

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Navigation Menu

Which scope/s are relevant/related to the feature request?

navigation-menu

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Notification component

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

I have created a component with tailwind to display notification number:

import { ChangeDetectionStrategy, Component, Input } from '@angular/core';

@Component({
  selector: 'code-up-notification',
  standalone: true,
  template: `
    <div
      class="absolute rounded-full -top-2 -end-2 inline-flex items-center justify-center w-6 h-6 text-[0.6rem] font-bold text-white bg-destructive">
      {{ _nbNotifications }}
    </div>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush,
})
export class NotificationComponent {
  _nbNotifications = '';

  @Input({ required: true })
  set nbNotifications(nb: number) {
    this._nbNotifications = nb >= 99 ? '99+' : `${nb}`;
  }
}

Render:
image

It's just an idea for spartan.

and the parent:

<button
                hlmBtn
                class="relative"
                size="icon"
                variant="secondary">
                <hlm-icon size="sm" name="radixChatBubble" />
                <code-up-notification [nbNotifications]="98"/>
</button>

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Date Picker

Which scope/s are relevant/related to the feature request?

date-picker

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Select

Which scope/s are relevant/related to the feature request?

select

Information

Select

Deps

  1. CDKList
  2. CDKOverlay

brn

API (Proposed)

<brn-select formControlName="fruit" placeholder="Select a Fruit">
  <brn-select-trigger>
     <brn-select-value />
  </brn-select-trigger>
  <brn-select-content class="w-56">
      <brn-option value="Refresh">Refresh</fue-option>
      <brn-option value="Settings">Settings</fue-option>
      <brn-option value="Help">Help</fue-option>
      <brn-option value="Signout">Sign out</fue-option>
   </brn-select-content>
</brn-select>

1. brn-select - (Component)

Will be the primary parent component and will also be the point of contact for interfacing with the the underlying ngControl and other "input/form control" attributes that may need to be passed to the component. Radix and Shadcn have placeholder set on the select.value component but I think it's cleaner and more predictable to pass these things to the parent but I dont know would definitely like to hear from others.

Proposed Inputs

Input Type Default Description
placeholder string "" placeholder text
multiple boolean false Enables multi option selection
disable boolean false Enables or disables form control. Probably only really useful for ngModel since in reactive we will set the control as disabled

We can add anything else. I know looking at the mat-select they also include some eventEmitters we can also implement some of those if we think they would be useful. Maybe the emitting the open and close of select overlay would be useful as well.

2. brn-select-trigger - (Component)

Holds the button and will trigger open and closing the cdkOverlay.

Note: Reason why I believe we may need to use cdkOverlay over menu is I found cdkMenu to be a little limited especially when it came to allowing multi-selection. Maybe this something that can be changed I didnt find a way to keep it open after making a selection. I think mainly an issue with trying to use cdkMenuItem and cdkListboxOption at same time.

4. brn-select-value - (Component)

This component will simply display the current value for the select component. If no value we will display a provided placeholder text. User can also optionally exclude brn-select-value and instead pass there only template or html and can render the value using the formcontrol's value with either ngModel or reactive form group

5. brn-select-content (Directive)

Has cdkListbox as a host directive. Will listen for Listbox value changes and publish the value updates. Will also be responsible for setting additional attributes tot he cdkListbox such as a multiple and setting the focus on the cdkListbox when the overlay is opened. As well as setting aria label id's controlledBy and LabelledBy

6. brn-select-option (component)

CdkOption as a host directive, so this is a component primarily for holding the svg checkbox to show hide when an option is selected (Reason for component instead of directive). Not sure if you would want this to be more flexible in terms of being able to provide something custom

Input Type Default Description
value string "" Really just a passthrough for the value input for cdkOption directive

7. brn-select-separator (directive)

Unsure of this one, this may just be a helm since I dont believe there is any physical element or divider rendered just spacing from what I can tell

8. brn-select-scroll-up (directive)

Can probably just have a a mouseenter hostbinding to trigger and change the option focus on cdkList

9. brn-select-scroll-down (directive)

same as number 7

10. brn-select-service (Service)

With most of these component being passed as content children, much easier to manage all the state in a service. We can use a signal object like shown below.

state = signal<{
  id: string;
  labelId: string;
  panelId: string;
  placeholder: string;
  isExpanded: boolean;
  multiple: boolean;
  disabled: boolean;
  value: string | string[];
}>({
  id: "",
  labelId: "",
  panelId: "",
  placeholder: "",
  isExpanded: false,
  multiple: false,
  disabled: false,
  value: "",
});

We can have accessor's like this. Took inspiration for this from something I saw Joshua Morony do recently when using signals and some light state management. We can do this differently if needed just thought it would be interesting to try.

Reference: https://youtu.be/ol671CJnNjY?si=o5hYEJ_8dRzrvWJS

id = computed(() => this.state().id);
labelId = computed(() => this.state().labelId);
panelId = computed(() => this.state().panelId);
placeholder = computed(() => this.state().placeholder);
disabled = computed(() => this.state().disabled);
isExpanded = computed(() => this.state().isExpanded);
multiple = computed(() => this.state().multiple);
value = computed(() => this.state().value);

We also need a subject to emit changes from cdkListbox to the service and update the state in the service. All other components will just read the value from state signal.

listBoxValueChangeEvent$ = new Subject<ListboxValueChangeEvent<any>>();

hlm

We can have one for each brn component/directive to add associated styles with an addition to these

1. hlm-select-group (directive)

2. hlm-select-label (directive)

Aria

  1. Label - User can pass a label within select parent and we can manually assign an id if one is not provided to the label element. If no label element then select component can generate one an invisible one based on placeholder. Can correct me but i believe its usually best practice to have some sort of label

  2. we can generate id's for the select trigger and select content elements and provide controls and controlledBy + LabelledBy respectively.

  3. Aria expanded on select-trigger and role of combobox

  4. cdkList is taking care of all the Listbox ADA and keyboard navigation so no need to worry much about that.

Radix

<Select.Root>
  <Select.Trigger className="SelectTrigger" aria-label="Food">
    <Select.Value placeholder="Select a fruitโ€ฆ" />
    <Select.Icon className="SelectIcon">
      <ChevronDownIcon />
    </Select.Icon>
  </Select.Trigger>
  <Select.Portal>
    <Select.Content className="SelectContent">
      <Select.ScrollUpButton className="SelectScrollButton">
        <ChevronUpIcon />
      </Select.ScrollUpButton>
      <Select.Viewport className="SelectViewport">
        <Select.Group>
          <Select.Label className="SelectLabel">Fruits</Select.Label>
          <SelectItem value="apple">Apple</SelectItem>
          <SelectItem value="banana">Banana</SelectItem>
          <SelectItem value="blueberry">Blueberry</SelectItem>
          <SelectItem value="grapes">Grapes</SelectItem>
          <SelectItem value="pineapple">Pineapple</SelectItem>
        </Select.Group>

        <Select.Separator className="SelectSeparator" />

        <Select.Group>
          <Select.Label className="SelectLabel">Vegetables</Select.Label>
          <SelectItem value="aubergine">Aubergine</SelectItem>
          <SelectItem value="broccoli">Broccoli</SelectItem>
          <SelectItem value="carrot" disabled>
            {" "}
            Carrot{" "}
          </SelectItem>
          <SelectItem value="courgette">Courgette</SelectItem>
          <SelectItem value="leek">Leek</SelectItem>
        </Select.Group>

        <Select.Separator className="SelectSeparator" />

        <Select.Group>
          <Select.Label className="SelectLabel">Meat</Select.Label>
          <SelectItem value="beef">Beef</SelectItem>
          <SelectItem value="chicken">Chicken</SelectItem>
          <SelectItem value="lamb">Lamb</SelectItem>
          <SelectItem value="pork">Pork</SelectItem>
        </Select.Group>
      </Select.Viewport>
      <Select.ScrollDownButton className="SelectScrollButton">
        <ChevronDownIcon />
      </Select.ScrollDownButton>
    </Select.Content>
  </Select.Portal>
</Select.Root>

Source

https://www.radix-ui.com/primitives/docs/components/select

Shadcn

<Select>
  <SelectTrigger className="w-[180px]">
    <SelectValue placeholder="Select a fruit" />
  </SelectTrigger>
  <SelectContent>
    <SelectGroup>
      <SelectLabel>Fruits</SelectLabel>
      <SelectItem value="apple">Apple</SelectItem>
      <SelectItem value="banana">Banana</SelectItem>
      <SelectItem value="blueberry">Blueberry</SelectItem>
      <SelectItem value="grapes">Grapes</SelectItem>
      <SelectItem value="pineapple">Pineapple</SelectItem>
    </SelectGroup>
  </SelectContent>
</Select>

Source

https://ui.shadcn.com/docs/components/select

Other Considerations

Since we are not making use of cdkMenu in this proposal and are instead using cdkOverlay directly we may need to handle some issue with opening up or down depending on space. Can probably just look at mat-select for an idea.

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Bug: .env isn't added to the .gitignore

Please provide the environment you discovered this bug in.

Locally, after installing via nx-create-workspace

Which area/package is the issue in?

Don't know / other

Description

Following the Installation docs, there's a point where it's suggested to create a .env file in the project root. But the gitignore doesn't reference that file, so newer folks might accidentally commit it.

Please provide the exception or error you saw

No response

Other information

Maybe this belongs in the analogjs/platform repo instead. Let me know if that's the case. An alternative fix would be to add a comment in the docs reminding them not to commit sensitive files to their repo...

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Avatar

Avatar

Assesment

Time Complexity Risk Description
2 2 1 Unless I'm missing something, it's really just a bunch of styles & not much else with 0 dependencies

Dependencies

None

brn


API (proposed)

I think the shadcn/radix approach are good here, so I'd just use that. Only thing I'm not sure of is using components vs directives. I'm leaning towards directives, since they can provide some extra flexibility (img/picture use for AvatarImage & wouldn't require us managing default html attributes like alt/src etc.) but that's not really a big deal.

The fallback should be shown if no image template is provided or if the image is provided but emits an onError event.

<!--Component approach-->

<brn-avatar>
  <brn-avatar-image src="..." alt="..." />
  <brn-avatar-fallback>JD</brn-avatar-fallback>
</brn-avatar>

<!--Directive approach-->

<brn-avatar>
  <img brnAvatarImage src="..." alt="..." />
  <span brnAvatarFallback>JD</span>
</brn-avatar>

Id also provide a few helpers for the fallback, like an initials pipe & an avatars component.

const toInitial = (word: string): string => word[0].toLocaleUpperCase();

const firstAndLast = (words: string[], firstAndLastOnly = true) => firstAndLastOnly ? [words[0], words[words.length - 1]] : words;

// demo impl, needs a bit more work
@Pipe({
  name: 'initials'
  standalone: true
})
export class InitialsPipe implements PipeTransform {
  transform(name: string, firstAndLastOnly = true, delimiter = " "): string {
    return firstAndLast(name.split(delimiter), firstAndLastOnly).filter(Boolean).map(toInitial).join("").trim();
  }
}

The avatars component would simply be a container for multiple avatars & partially overlay them on top of each other.

<!--This would display the max as a pure text component-->
<brn-avatars [max]="5">
  <brn-avatar *ngFor="let user of users">
    <img brnAvatarImage [src]="user.profileImg" [alt]="user.name + ' profile'" />
    <span brnAvatarFallback>{{user.name | initials}}</span>
  </brn-avatar>
  <span brnAvatarsMax>+{{users.length - 5}}</span>
</brn-avatars>

<!--or as an avatar-->
<brn-avatars [max]="5">
  <brn-avatar *ngFor="let user of users">
    <img brnAvatarImage [src]="user.profileImg" [alt]="user.name + ' profile'" />
    <span brnAvatarFallback>{{user.name | initials}}</span>
  </brn-avatar>
  <brn-avatar brnAvatarsMax>
    <span brnAvatarFallback>&plus;{{users.length - 5}}</span>
  </brn-avatar>
</brn-avatars>

Proposed inputs (avatarFallback)

Input Type Default Description
autoColor string/undefined undefined Auto-gens a background color & text-contrast color based on provided text content

Proposed inputs (avatars)

Input Type Default Description
max number/ undefined undefined The maximum number of avatars to display (if more they get displayed as the brnAvatarsMax component)

hlm


Not sure what hlm would do here, radix ui styles the component as much as I think is required, but if there are any styles you can think of, let me know. It can of course still just be re-exported as a hml component for api convenience.

Aria


Roles, States & Properties

Can't find much on aria rules for avatars, the image should have an alt tag of course, but that's about it & even that would be on the user if we go the directive route.

Radix


API

import * as Avatar from "@radix-ui/react-avatar";

export default () => (
  <Avatar.Root>
    <Avatar.Image />
    <Avatar.Fallback />
  </Avatar.Root>
);

Source

https://www.radix-ui.com/docs/primitives/components/avatar

#shadcn


API

import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";

<Avatar>
  <AvatarImage src="https://github.com/shadcn.png" />
  <AvatarFallback>CN</AvatarFallback>
</Avatar>;

Source

https://ui.shadcn.com/docs/components/avatar

refactor: Auto-generate Table of Contents (ToC)

Which scope/s are relevant/related to the feature request?

Docs

Information

Currently, the ToC for every page ("On this page") is added manually instead of being generated from markdown. That is because we don't use MD/MDX for authoring content here yet.

I was following this issue (analogjs/analog#448) regarding the lack of support for Angular components in markdown in Analog, and I started looking at the code here to see how Spartan handled this, especially common things like the Table of Contents, which I thought was a plugin.

I tried an initial solution that works in SSG mode (with disabled JavaScript) and will raise a Draft MR for review.

Describe any alternatives/workarounds you're currently using

N/A

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Installation via Angular CLI does not provide latest brn and hlm components versions.

Please provide the environment you discovered this bug in.

Installed helm components and directives are not in the latest version (missing base component and content component for example for dialog or alertdialog) and brain dependencies are not in the latest version.

Which area/package is the issue in?

dialog

Description

Missing hlm-dialog.component.ts and hlm-dialog-content.component.ts at install. Same for alertdialog.
Does not match the documentation.

Please provide the exception or error you saw

No response

Other information

P.S. : thank you for creating this. โค๏ธ
Do not have time to contribute right now, but 300 have not been reached yet... ๐Ÿ˜„

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Components doesn't work in Brave browser

Please provide the environment you discovered this bug in.

Hi All,
i didn't find any specification a about browser support. I took a look around in the components section and Accordion failed to open in this Brave Version 1.61.109 Chromium: 120.0.6099.144 (Official Build) (64-bit).

In Opera work all fine. Opera One(version: 105.0.4970.21)

Cheers!

Which area/package is the issue in?

accordion

Description

Component doesn't open on click event

Please provide the exception or error you saw

ERROR TypeError: Cannot read properties of null (reading 'firstChild')
    at S9 (vendor-e35b74e6.js:7:96783)
    at I9 (vendor-e35b74e6.js:7:96992)
    at al (vendor-e35b74e6.js:7:96300)
    at K9 (vendor-e35b74e6.js:7:106763)
    at hi (vendor-e35b74e6.js:7:106097)
    at Hi (vendor-e35b74e6.js:7:106601)
    at template (accordion.page-bc7ee3e4.js:1:2488)
    at Uv (vendor-e35b74e6.js:7:63551)
    at $d (vendor-e35b74e6.js:7:82071)
    at kD (vendor-e35b74e6.js:7:81875)

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Brn Badge

Badge

Assesment

Time Complexity Risk Description
2 3 0 Some styles, some logic, shouldn't be very hard :)

Dependencies

None

brn


API (proposed)

The badge directive would add a badge element to the dom & apply an aria-role of status to the parent element.

<button brnBadge="5">Click</button>
type BadgePosition = "above after" | "above before" | "below before" | "below after" | "before" | "after" | "above" | "below";

type BadgeSize = "small" | "medium" | "large";

Proposed inputs

Input Type Default Description
brnBadge string/number/null/undefined empty_string the badges content
brnBadgeDescription string empty_string the description to add to the aria-describedBy
brnBadgeHidden boolean false hides the badge - this could also be removed and simply done when badge content is null or undefined
brnBadgeOverlap boolean true overlaps the badge on the parent element
brnBadgePosition BadgePosition 'above after' the position of the badge relative to the parent element
brnBadgeSize BadgeSize 'small' the size of the badge

hlm


Already implemented :) I'd just compose it with the proposed brn directive.

Aria


Roles, States & Properties

Radix doesn't seem to have a badge component, looking around the web I've seen role="status" being applied, which seems reasonable to me.
Angular material also uses a description attribute to add an aria-describedBy to the parent elemnt, which seems like a good approach.
aria-label can be used, but should be provided by the user.

Radix


N/A

shadcn


API

import { Badge } from "@/components/ui/badge";

<Badge variant="outline">Badge</Badge>;

Source

https://ui.shadcn.com/docs/components/badge

RFC: spartan/stack Nx plugin

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

The goal is to set up a spartan project with a single CLI command.

I would say it should be as easy as

npx create-nx-workspace --preset=spartan

or something similar.

This would do the following:

  1. Create Nx workspace with libs/apps
  2. Configure AnalogJs with Tailwind & tRPC (this could be done with the Analog preset)
  3. Install Drizzle
  4. Add a .env file with the DB connection placeholder
  5. Do something with supabase? (Not sure about this one since all we use it for right now is as our infrastructure provider for the DB)
  6. Add a basic template that showcases the functionality

Another idea is to add a docker-compose.yml file that spins up a local Postgres DB?

Describe any alternatives/workarounds you're currently using

You can run

npx create-nx-workspace --preset=@analogjs/platform:app

and opt into tailwind and tRPC, which get's you half way there.

Then you need to manually install Drizzle and set it up.
You need to set up a Supabase account.
You need to download the Postgres docker image and run the container locally or set up the supabase CLI.

I would be willing to submit a PR to fix this issue

  • Yes
  • No

HlmSheetModule is missing HlmSheetHeaderComponent

Please provide the environment you discovered this bug in.

After a fresh install and importing the HlmSheetModule the HlmSheetHeaderComponent could not be resolved.

Which area/package is the issue in?

sheet

Description

After a fresh install and importing the HlmSheetModule the HlmSheetHeaderComponent could not be resolved.

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Bug: Getting HTMLElement is not defined error

Please provide the environment you discovered this bug in.

Ubuntu 22.04.3 LTS
Node Version - v18.14.2

Which area/package is the issue in?

Don't know / other

Description

Getting the below error:

Step 1 - run yarn dev
Step 2- Goto http://127.0.0.1:4200/

Screenshot from 2023-08-13 13-10-11

image

Please provide the exception or error you saw

An error occured while server rendering /index.html:

	HTMLElement is not defined
ReferenceError: HTMLElement is not defined
    at <static_initializer> (/home/deepak/Desktop/PW/OpenSource/spartan/projects/ngx-scrollbar/src/lib/utils/ng-attr.directive.ts:201:169)
    at /home/deepak/Desktop/PW/OpenSource/spartan/projects/ngx-scrollbar/src/lib/utils/ng-attr.directive.ts:160:4

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Tailwind classes not merging correctly in avatar fallback

Please provide the environment you discovered this bug in.

@angular/core: "16.2.6"
@spartan-ng/ui-core: "0.0.1-alpha.309"
@spartan-ng/ui-avatar-brain: "0.0.1-alpha.309"

Which area/package is the issue in?

avatar

Description

When using OnPush change detection, custom classes to brnAvatarFallback don't merge correctly.

Simple reproduction:

@Component({
  selector: 'app-root',
  standalone: true,
  imports: [HlmAvatarComponent, HlmAvatarFallbackDirective],
  template: `
    <hlm-avatar>
      <span class='bg-destructive text-white' hlmAvatarFallback>RG</span>
    </hlm-avatar>
  `,
  changeDetection: ChangeDetectionStrategy.OnPush
})
export class AppComponent {}

With this example, we see that the generated class has both bg-destructive and bg-muted.
image

Removing or trigering change detection fixed the issue. I think it has something to do with the inputs settings a signal instead of generating the class directly. Guess we'll have to wait for signal inputs :(

Please provide the exception or error you saw

No response

Other information

This may happen in other primitives that uses signals as classes like Aspect Ratio and Icon, but I haven't tested it.

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Cannot read properties of undefined when copying the primitives

Please provide the environment you discovered this bug in.

Created an NX based AnalogJS project with the defaults and added the UI components as described in the docs.s

Which area/package is the issue in?

Don't know / other

Description

When I wanted to copy the primitives it gave me an error. It is the same if I choose all (or select every option I'd like)

Please provide the exception or error you saw

โˆš Choose which primitives you want to copy ยท accordion, alert, alertdialog, aspectratio, avatar, badge, button, card, command, dialog, icon, input, label, menu, popover, progress, radiogroup, scrollarea, separator, sheet, skeleton, spinner, switch, tabs, toggle, typography, table, hovercard, collapsible, menubar, contextmenu

โˆš Some of the primitives you are trying to install depend on the icon primitive. Do you want to add it to your project? (Y/n) ยท true  
โˆš Some of the primitives you are trying to install depend on the button primitive. Do you want to add it to your project? (Y/n) ยท true
โˆš The context menu is implemented as part of the menu-helm primitive. Adding menu primitive. (y/N) ยท true
โˆš The menubar is implemented as part of the menu-helm primitive. Adding menu primitive. (y/N) ยท true

 >  NX   Cannot read properties of undefined (reading 'internalName')  


TypeError: Cannot read properties of undefined (reading 'internalName')
    at C:\Users\Shadow\Documents\Projects\thd\node_modules\@spartan-ng\nx\src\generators\ui\generator.js:44:68
    at Generator.next (<anonymous>)
    at fulfilled (C:\Users\Shadow\Documents\Projects\thd\node_modules\tslib\tslib.js:166:62)
    at process.processTicksAndRejections (node:internal/process/task_queues:95:5)

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Doc on avatar

Please provide the environment you discovered this bug in.

In prod on https://www.spartan.ng/components/avatar loading section.

Which area/package is the issue in?

avatar

Description

you can change the doc

from

import { Component } from '@angular/core';
import { HlmAvatarComponent } from '@spartan-ng/ui-avatar-helm';

@Component({
  selector: 'spartan-avatar-preview',
  standalone: true,
  imports: [HlmAvatarComponent],
  template: `
    <hlm-avatar>
      <img src='/assets/avatar.png' alt='spartan logo. Resembling a spartanic shield' hlmAvatarImage />
      <span class='bg-destructive text-white' hlmAvatarFallback>RG</span>
    </hlm-avatar>
  `,
})
export class AvatarPreviewComponent {}

to

import { Component } from '@angular/core';
import { HlmAvatarComponent, HlmAvatarFallbackDirective, HlmAvatarImageDirective } from '@spartan-ng/ui-avatar-helm';

@Component({
	selector: 'spartan-avatar-preview',
	standalone: true,
	imports: [HlmAvatarImageDirective, HlmAvatarComponent, HlmAvatarFallbackDirective],
	template: `
		<hlm-avatar variant="large">
			<img src="/assets/avatar.png" alt="spartan logo. Resembling a spartanic shield" hlmAvatarImage />
			<span class="bg-[#FD005B] text-white" hlmAvatarFallback>RG</span>
		</hlm-avatar>
	`,
})
export class AvatarPreviewComponent {}

Same issue for Usage part

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Collapsible

Which scope/s are relevant/related to the feature request?

collapsible

Information

Checkbox

Assesment

Time Complexity Risk Description
3 2 0 Refactor Accordion to extract collapsible

Dependencies

Becomes dependency of accordion

brn

API (proposed)

Trigger is a directive only to be applied to button. Only button due to aria concerns.

<brn-collapsible hlm>
           <div>
            <span>What is SPARTAN</span>
            <button brnCollapsibleTrigger><span class="sr-only">Toggle collapsible</span><hlm-icon name="collapse" /></button>. 
          </div>
        <brn-collapbisble-content>
          It is a collection of full-stack technologies that provide end-to-end type-safety.
        </brn-collapsible-content>
      </brn-collapsible>

Proposed inputs

Input Type Default Description
open BooleanInput false allows to programmatically change if collapsible is open

hlm


This is a brain only component.

Aria


Roles, States & Properties

Keyboard Interaction

When the disclosure control has focus:

  • Enter: activates the disclosure control and toggles the visibility of the disclosure content.
  • Space: activates the disclosure control and toggles the visibility of the disclosure content.

WAI-ARIA Roles, States, and Properties

  • The element that shows and hides the content has role button.
  • When the content is visible, the element with role button has aria-expanded set to true. When the content area is hidden, it is set to false.
  • Optionally, the element with role button has a value specified for aria-controls that refers to the element that contains all the content that is shown or hidden.

disclosure

Radix


API

export default () => (
  <Collapsible.Root>
    <Collapsible.Trigger />
    <Collapsible.Content />
  </Collapsible.Root>
);

Source

https://www.radix-ui.com/docs/primitives/components/collapsible

shadcn


API

export default () => (
  <Collapsible.Root>
    <Collapsible.Trigger />
    <Collapsible.Content />
  </Collapsible.Root>
);

Source

https://ui.shadcn.com/docs/components/collapsible

Describe any alternatives/workarounds you're currently using

Does not exist yet.

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Checkbox component can't find module '@spartan-ng/ui-icon-helm

Please provide the environment you discovered this bug in.

Latest version 0.0.1-alpha.318

Which area/package is the issue in?

checkbox

Description

checkbox
missing peerdependencies for checkbox component

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

bug: select/option in dark mode is white text on white background

Please provide the environment you discovered this bug in.

The "Framework" dropdown in the first "Preview" tab in https://www.spartan.ng/components/card

Seen in Firefox & Microsoft Edge, on Windows 11.

Which area/package is the issue in?

select

Description

Using [hlmInput] on select turns the black text to white, but the background remains white.

A "proper" solution is to implement select, currently marked under "soon". There should be a simpler alternative CSS fix too, but then we would need to remember to add that for anywhere else we might add select/option later.

Please provide the exception or error you saw

_No response_

Other information

Working in light mode:

image

image

Not working in dark mode:

On Microsoft Edge
On Firefox

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Bug: Issues with 'yarn nx serve analog-trpc' command mentioned in Readme file

Please provide the environment you discovered this bug in.

Ubuntu 22.04.3 LTS
Node Version - v18.14.2

Which area/package is the issue in?

Docs

Description

I'm encountering problems with the 'yarn nx serve analog-trpc' in the project. When running 'yarn nx serve analog-trpc', the development server doesn't seem to start as expected
image

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Opening sheet triggers twice

Please provide the environment you discovered this bug in.

Clean install of the repo.

Which area/package is the issue in?

sheet

Description

Hey,

I was tinkering with the sheet component and saw something interesting. When you click on the sheet component in Storybook and then click on the "Edit Profile" button. Opening the sheet actually triggers twice.

This is because in the BrnSheetTriggerDirective, we listen to the click on the host and execute the open method, which triggers opening the sheet once.

But the BrnSheetTriggerDirective is also extending the BrnDialogTriggerDirective which listens for a click as well and executes the open method.

I think we can remove the line '(click)': 'open()', from the BrnSheetTriggerDirective, since it's already defined within the BrnDialogTriggerDirective. Maybe we could remove the other duplications as well or am I missing something?

I can create a PR if you guys agree :)

Please provide the exception or error you saw

Log the `open` method in `BrnSheetTriggerDirective` and see it triggering twice.

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Drawer

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Adding primitives : Cannot read properties of null (reading 'generators')

Please provide the environment you discovered this bug in.

Blank new project using Spartan UI and tailwind

Which area/package is the issue in?

Don't know / other

Description

Getting an error while adding primitives, whether installing "all" or just a single one.
Blank new project using bun, but same error with npm (even after running npm i for uninstalled dev dependencies)
Both following commands lead to the same error :
--> npx nx g @spartan-ng/nx:ui --verbose
--> npx nx g @spartan-ng/nx:ui card

Please provide the exception or error you saw

image

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Form

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

Pretty set on using ng-signal-forms here, but open to explore other options that ideally are signal driven!

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

[DROPDOWN MENU] Doesn't have an opening/closing animation

Please provide the environment you discovered this bug in.

New/Pure nx workspace installation with all components.

Which area/package is the issue in?

dropdown-menu

Description

The Helm directives contain classes which should animate the menu by checking the state and side data attributes, however these are not existing on the component itself.

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Tooltip

brn


API (proposed)

I'd go with a material like approach, since global options in angular can be provided through DI & so a root component as seen in Radix/Shadcn is not required.

<button (click)="doesStuff()" brnTooltip="Additional information" #tt="brnTooltip" brnTooltipPosition="before" ...>Click</button>

<button (click)="tt.show()">Show tooltip</button>

Proposed types

import { BooleanInput } from "@angular/cdk/coercion";

// before and after consider the direction of the text.
type TooltipPosition = "above" | "below" | "left" | "right" | "before" | "after";

type TooltipDisplayStrategy = "always" | "onHover" | "onFocus";

type TooltipDefaultOptions = {
  position: TooltipPosition; // default: 'below'
  positionAtOrigin: BooleanInput; // default: false
  showDelay: number; // default: 0
  hideDelay: number; // default: 0
  tooltipClass: string; // default: ''
  displayStrategy: TooltipDisplayStrategy; // default: 'always'
};

// directive exportAs reference
interface Tooltip {
  show(delayMilis?: number): void;
  hide(delayMilis?: number): void;
  toggle(delayMilis?: number): void;
}

Global options, can be overridden by overriding providers or by directive inputs

Input Type Default Description Global Property
brnTooltip string - The text to display in the tooltip -
brnTooltipPosition TooltipPosition 'below' The position of the tooltip relative to the origin position
brnTooltipPositionAtOrigin boolean false Whether the tooltip should be positioned at the origin or at the mouse cursor positionAtOrigin
brnTooltipShowDelay number 0 The delay in miliseconds before the tooltip is shown showDelay
brnTooltipHideDelay number 0 The delay in miliseconds before the tooltip is hidden hideDelay
brnTooltipClass string '' The class to apply to the tooltip tooltipClass
brnTooltipDisplayStrategy TooltipDisplayStrategy 'always' The strategy to use for displaying the tooltip displayStrategy

hlm


Not sure what hlm would do here, as it's a purely logical component. If you have any style/variant ideas, I'm all ears. :) We could however just re-export every brn-only component as a hlm component for the sake of api consistency for hlm users.

Aria


Roles, States & Properties

  • The tooltip itself should have role="tooltip" and aria-hidden="true" by default.
  • the element the tooltip is applied to should have aria-describedby set to the tooltip's id.
    • aria-describedby should be appended if it already exists, since it allows for multiple element id's to be specified. (Ex. a badge and a tooltip on a notifications icon-button)
    • Technically a tooltip can be used as a label, this should then replace the aria-describedby with aria-labelledby and the tooltip should have role="label" instead of role="tooltip". This is however not recommended & doesn't seem to be covered by radix/shadcn or angular material.
  • The tooltip should always disapear on esc keypress.
  • The tooltip should appear on focus and hover events to cover keyboard & mouse users.
    • When the tooltip is triggered by hover it should be dismissed on mouseleave or esc keypress.
    • When the tooltip is triggered by focus it should be dismissed on blur or esc keypress.
      • The tooltip can be annoying to keyboard users on focus, so an additional keypress is sometimes used to show the tooltip (Ex. f2 when focused). We can make this optional by adding a autoShowOnFocus boolean option or a requireKeyOnFocus string option, which would show the tooltip on focus immediately if undefined.

Source

https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
w3c/aria-practices#128
https://inclusive-components.design/tooltips-toggletips/

Radix


API

import React from "react";
import * as Tooltip from "@radix-ui/react-tooltip";
import { PlusIcon } from "@radix-ui/react-icons";
import "./styles.css";

const TooltipDemo = () => {
  return (
    <Tooltip.Provider>
      <Tooltip.Root>
        <Tooltip.Trigger asChild>
          <button className="IconButton">
            <PlusIcon />
          </button>
        </Tooltip.Trigger>
        <Tooltip.Portal>
          <Tooltip.Content className="TooltipContent" sideOffset={5}>
            Add to library
            <Tooltip.Arrow className="TooltipArrow" />
          </Tooltip.Content>
        </Tooltip.Portal>
      </Tooltip.Root>
    </Tooltip.Provider>
  );
};

export default TooltipDemo;

Source

https://www.radix-ui.com/docs/primitives/components/tooltip

shadcn


API

import { Plus } from "lucide-react";

import { Button } from "@/components/ui/button";
import { Tooltip, TooltipContent, TooltipProvider, TooltipTrigger } from "@/components/ui/tooltip";

export function TooltipDemo() {
  return (
    <TooltipProvider>
      <Tooltip>
        <TooltipTrigger asChild>
          <Button variant="outline" className="w-10 rounded-full p-0">
            <Plus className="h-4 w-4" />
            <span className="sr-only">Add</span>
          </Button>
        </TooltipTrigger>
        <TooltipContent>
          <p>Add to library</p>
        </TooltipContent>
      </Tooltip>
    </TooltipProvider>
  );
}

Source

https://ui.shadcn.com/docs/components/tooltip

User Class should not be set to Host too e.g. hlm-accordion-trigger

Please provide the environment you discovered this bug in.

spartan 0.0.1-alpha.313
"@angular/core": "17.0.2",

Which area/package is the issue in?

accordion
sheet
dialog
alert-dialog

Description

Wenn setting a class on a hlmAccordionTrigger the class is set to the button of the brn-accordion-trigger and to the brn-accordion-trigger.
image

Please provide the exception or error you saw

In this case the padding is added twice wich is not expected.

Other information

My idea would be, that the class should not be set to the host (button) in this case.
The userCls should not be set via setClassToCustomElement to the host.

@goetzrobin if you also think would be better i would take a look on this.

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Pagination

Which scope/s are relevant/related to the feature request?

pagination

Information

Pagination

him

Aria

  1. use nav, role="navigation", aria-label="pagination"
  2. use ul & li
  3. use a-tag & aria-current= "page"

Shadcn

import * as React from "react"
import {
  ChevronLeftIcon,
  ChevronRightIcon,
  DotsHorizontalIcon,
} from "@radix-ui/react-icons"

import { cn } from "@/lib/utils"
import { ButtonProps, buttonVariants } from "@/components/ui/button"

const Pagination = ({ className, ...props }: React.ComponentProps<"nav">) => (
  <nav
    role="navigation"
    aria-label="pagination"
    className={cn("mx-auto flex w-full justify-center", className)}
    {...props}
  />
)
Pagination.displayName = "Pagination"

const PaginationContent = React.forwardRef<
  HTMLUListElement,
  React.ComponentProps<"ul">
>(({ className, ...props }, ref) => (
  <ul
    ref={ref}
    className={cn("flex flex-row items-center gap-1", className)}
    {...props}
  />
))
PaginationContent.displayName = "PaginationContent"

const PaginationItem = React.forwardRef<
  HTMLLIElement,
  React.ComponentProps<"li">
>(({ className, ...props }, ref) => (
  <li ref={ref} className={cn("", className)} {...props} />
))
PaginationItem.displayName = "PaginationItem"

type PaginationLinkProps = {
  isActive?: boolean
} & Pick<ButtonProps, "size"> &
  React.ComponentProps<"a">

const PaginationLink = ({
  className,
  isActive,
  size = "icon",
  ...props
}: PaginationLinkProps) => (
  <a
    aria-current={isActive ? "page" : undefined}
    className={cn(
      buttonVariants({
        variant: isActive ? "outline" : "ghost",
        size,
      }),
      className
    )}
    {...props}
  />
)
PaginationLink.displayName = "PaginationLink"

const PaginationPrevious = ({
  className,
  ...props
}: React.ComponentProps<typeof PaginationLink>) => (
  <PaginationLink
    aria-label="Go to previous page"
    size="default"
    className={cn("gap-1 pl-2.5", className)}
    {...props}
  >
    <ChevronLeftIcon className="h-4 w-4" />
    <span>Previous</span>
  </PaginationLink>
)
PaginationPrevious.displayName = "PaginationPrevious"

const PaginationNext = ({
  className,
  ...props
}: React.ComponentProps<typeof PaginationLink>) => (
  <PaginationLink
    aria-label="Go to next page"
    size="default"
    className={cn("gap-1 pr-2.5", className)}
    {...props}
  >
    <span>Next</span>
    <ChevronRightIcon className="h-4 w-4" />
  </PaginationLink>
)
PaginationNext.displayName = "PaginationNext"

const PaginationEllipsis = ({
  className,
  ...props
}: React.ComponentProps<"span">) => (
  <span
    aria-hidden
    className={cn("flex h-9 w-9 items-center justify-center", className)}
    {...props}
  >
    <DotsHorizontalIcon className="h-4 w-4" />
    <span className="sr-only">More pages</span>
  </span>
)
PaginationEllipsis.displayName = "PaginationEllipsis"

export {
  Pagination,
  PaginationContent,
  PaginationLink,
  PaginationItem,
  PaginationPrevious,
  PaginationNext,
  PaginationEllipsis,
}

Source

https://ui.shadcn.com/docs/components/pagination

Other Considerations

Since we rely on html tags here to drive accessibility we should probably make most of the styling come from directives

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Alert Dialog

brn

API (proposed)

<brn-alert-dialog>
  <button brnAlertDialogTrigger>Open</button>
  <div *brnAlertDialogContent>
    <brn-alert-dialog-header>
      <h3 brnAlertDialogTitle>Are you absolutely sure?</h3> <!-- could also just make it a brn-alert-dialog-title component -->
      <p brnAlertDialogDescription>  <!-- could also just make it a brn-alert-dialog-description component -->
        This action cannot be undone. This will permanently delete your account
        and remove your data from our servers.
      </p>
    </brn-alert-dialog-header>
    <brn-alert-dialog-footer>
      <button brnAlertDialogCancel>Cancel</button> <!-- or we expose a context, ctx variable in the template to do something like (click)="ctx.close()" -->
      <button brnAlertDialogAction>Continue</button> <!-- or we expose a context, ctx variable in the template to do something like (click)="ctx.action(anyDataYouWantToExposeOnSuccess)" -->
    </brn-alert-dialog-footer>
  </div>
</brn-alert-dialog>

Do we like the overlay being its own component? I could also see this being a structural directive that allows for some configuration through inputs it directly?

Helpful info

Dialog also allows to expose data to the dialog through dependency injection. I don't have anything against it, but might not even need that if we go the template route since we also have access to data/functions of the enclosing component.
We do have to be aware of Change Detection implications of this approach though as CD of enclosing component is used for dialog in this case, as far as I understand.

hlm

API (proposed)

<hlm-alert-dialog>
  <button hlmAlertDialogTrigger>Open</button>
  <div *hlmAlertDialogContent>
    <hlm-alert-dialog-header>
      <h3 hlmAlertDialogTitle>Are you absolutely sure?</h3> <!-- could also just make it a brn-alert-dialog-title component -->
      <p hlmAlertDialogDescription>  <!-- could also just make it a brn-alert-dialog-description component -->
        This action cannot be undone. This will permanently delete your account
        and remove your data from our servers.
      </p>
    </hlm-alert-dialog-header>
    <hlm-alert-dialog-footer>
      <button hlmAlertDialogCancel>Cancel</button> <!-- or we expose a context, ctx variable in the template to do something like (click)="ctx.close()" -->
      <button hlmAlertDialogAction>Continue</button> <!-- or we expose a context, ctx variable in the template to do something like (click)="ctx.action(anyDataYouWantToExposeOnSuccess)" -->
    </hlm-alert-dialog-footer>
  </div>
</hlm-alert-dialog>

Since this is a pretty complex component I would just expose the same API, but with a hlm prefix and maybe also expose some standalone directives that only apply the styles for all the different parts. We should aim for simplicity as most people might not need the available customization brought by the component + (optional style) directive, but instead want the simplest API

Helpful info


radix

API

import * as AlertDialog from '@radix-ui/react-alert-dialog';

export default () => (
  <AlertDialog.Root>
    <AlertDialog.Trigger />
    <AlertDialog.Portal>
      <AlertDialog.Overlay />
      <AlertDialog.Content>
        <AlertDialog.Title />
        <AlertDialog.Description />
        <AlertDialog.Cancel />
        <AlertDialog.Action />
      </AlertDialog.Content>
    </AlertDialog.Portal>
  </AlertDialog.Root>
);

Source

https://github.com/radix-ui/primitives/tree/main/packages/react/alert-dialog/src

Aria Design Pattern

Source

https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/

WAI-ARIA Roles, States, and Properties

  • The element that contains all elements of the dialog, including the alert message and any dialog buttons, has role alertdialog.
  • The element with role alertdialog has either:
    • A value for aria-labelledby that refers to the element containing the title of the dialog if the dialog has a visible label.
    • A value for aria-label if the dialog does not have a visible label.
  • The element with role alertdialog has a value set for aria-describedby that refers to the element containing the alert message.

Keyboard Interaction

In the following description, the term tabbable element refers to any element with a tabindex value of zero or greater. Note that values greater than 0 are strongly discouraged.

  • When a dialog opens, focus moves to an element inside the dialog. See notes below regarding initial focus placement.
  • Tab:
    • Moves focus to the next tabbable element inside the dialog.
    • If focus is on the last tabbable element inside the dialog, moves focus to the first tabbable element inside the dialog.
  • Shift + Tab:
    • Moves focus to the previous tabbable element inside the dialog.
    • If focus is on the first tabbable element inside the dialog, moves focus to the last tabbable element inside the dialog.
  • Escape: Closes the dialog.

Note

  1. When a dialog opens, focus moves to an element contained in the dialog. Generally, focus is initially set on the first focusable element. However, the most appropriate focus placement will depend on the nature and size of the content. Examples include:
    • If the dialog content includes semantic structures, such as lists, tables, or multiple paragraphs, that need to be perceived in order to easily understand the content, i.e., if the content would be difficult to understand when announced as a single unbroken string, then it is advisable to add tabindex="-1" to a static element at the start of the content and initially focus that element. This makes it easier for assistive technology users to read the content by navigating the semantic structures. Additionally, it is advisable to omit applying aria-describedby to the dialog container in this scenario.
    • If content is large enough that focusing the first interactive element could cause the beginning of content to scroll out of view, it is advisable to add tabindex="-1" to a static element at the top of the dialog, such as the dialog title or first paragraph, and initially focus that element.
    • If a dialog contains the final step in a process that is not easily reversible, such as deleting data or completing a financial transaction, it may be advisable to set focus on the least destructive action, especially if undoing the action is difficult or impossible. The Alert Dialog Pattern is often employed in such circumstances.
    • If a dialog is limited to interactions that either provide additional information or continue processing, it may be advisable to set focus to the element that is likely to be most frequently used, such as an OK or Continue button.
  2. When a dialog closes, focus returns to the element that invoked the dialog unless either:
    • The invoking element no longer exists. Then, focus is set on another element that provides logical work flow.
    • The work flow design includes the following conditions that can occasionally make focusing a different element a more logical choice:
      1. It is very unlikely users need to immediately re-invoke the dialog.
      2. The task completed in the dialog is directly related to a subsequent step in the work flow.
    • For example, a grid has an associated toolbar with a button for adding rows. The Add Rows button opens a dialog that prompts for the number of rows. After the dialog closes, focus is placed in the first cell of the first new row.
  3. It is strongly recommended that the tab sequence of all dialogs include a visible element with role button that closes the dialog, such as a close icon or cancel button.

Helpful info

shadcn

API

<AlertDialog>
  <AlertDialogTrigger>Open</AlertDialogTrigger>
  <AlertDialogContent>
    <AlertDialogHeader>
      <AlertDialogTitle>Are you absolutely sure?</AlertDialogTitle>
      <AlertDialogDescription>
        This action cannot be undone. This will permanently delete your account
        and remove your data from our servers.
      </AlertDialogDescription>
    </AlertDialogHeader>
    <AlertDialogFooter>
      <AlertDialogCancel>Cancel</AlertDialogCancel>
      <AlertDialogAction>Continue</AlertDialogAction>
    </AlertDialogFooter>
  </AlertDialogContent>
</AlertDialog>

Source

"use client"

import * as React from "react"
import * as AlertDialogPrimitive from "@radix-ui/react-alert-dialog"

import { cn } from "@/lib/utils"
import { buttonVariants } from "@/components/ui/button"

const AlertDialog = AlertDialogPrimitive.Root

const AlertDialogTrigger = AlertDialogPrimitive.Trigger

const AlertDialogPortal = ({
  className,
  children,
  ...props
}: AlertDialogPrimitive.AlertDialogPortalProps) => (
  <AlertDialogPrimitive.Portal className={cn(className)} {...props}>
    <div className="fixed inset-0 z-50 flex items-end justify-center sm:items-center">
      {children}
    </div>
  </AlertDialogPrimitive.Portal>
)
AlertDialogPortal.displayName = AlertDialogPrimitive.Portal.displayName

const AlertDialogOverlay = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Overlay>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Overlay>
>(({ className, children, ...props }, ref) => (
  <AlertDialogPrimitive.Overlay
    className={cn(
      "fixed inset-0 z-50 bg-background/80 backdrop-blur-sm transition-opacity animate-in fade-in",
      className
    )}
    {...props}
    ref={ref}
  />
))
AlertDialogOverlay.displayName = AlertDialogPrimitive.Overlay.displayName

const AlertDialogContent = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Content>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Content>
>(({ className, ...props }, ref) => (
  <AlertDialogPortal>
    <AlertDialogOverlay />
    <AlertDialogPrimitive.Content
      ref={ref}
      className={cn(
        "fixed z-50 grid w-full max-w-lg scale-100 gap-4 border bg-background p-6 opacity-100 shadow-lg animate-in fade-in-90 slide-in-from-bottom-10 sm:rounded-lg sm:zoom-in-90 sm:slide-in-from-bottom-0 md:w-full",
        className
      )}
      {...props}
    />
  </AlertDialogPortal>
))
AlertDialogContent.displayName = AlertDialogPrimitive.Content.displayName

const AlertDialogHeader = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col space-y-2 text-center sm:text-left",
      className
    )}
    {...props}
  />
)
AlertDialogHeader.displayName = "AlertDialogHeader"

const AlertDialogFooter = ({
  className,
  ...props
}: React.HTMLAttributes<HTMLDivElement>) => (
  <div
    className={cn(
      "flex flex-col-reverse sm:flex-row sm:justify-end sm:space-x-2",
      className
    )}
    {...props}
  />
)
AlertDialogFooter.displayName = "AlertDialogFooter"

const AlertDialogTitle = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Title>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Title>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Title
    ref={ref}
    className={cn("text-lg font-semibold", className)}
    {...props}
  />
))
AlertDialogTitle.displayName = AlertDialogPrimitive.Title.displayName

const AlertDialogDescription = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Description>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Description>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Description
    ref={ref}
    className={cn("text-sm text-muted-foreground", className)}
    {...props}
  />
))
AlertDialogDescription.displayName =
  AlertDialogPrimitive.Description.displayName

const AlertDialogAction = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Action>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Action>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Action
    ref={ref}
    className={cn(buttonVariants(), className)}
    {...props}
  />
))
AlertDialogAction.displayName = AlertDialogPrimitive.Action.displayName

const AlertDialogCancel = React.forwardRef<
  React.ElementRef<typeof AlertDialogPrimitive.Cancel>,
  React.ComponentPropsWithoutRef<typeof AlertDialogPrimitive.Cancel>
>(({ className, ...props }, ref) => (
  <AlertDialogPrimitive.Cancel
    ref={ref}
    className={cn(
      buttonVariants({ variant: "outline" }),
      "mt-2 sm:mt-0",
      className
    )}
    {...props}
  />
))
AlertDialogCancel.displayName = AlertDialogPrimitive.Cancel.displayName

export {
  AlertDialog,
  AlertDialogTrigger,
  AlertDialogContent,
  AlertDialogHeader,
  AlertDialogFooter,
  AlertDialogTitle,
  AlertDialogDescription,
  AlertDialogAction,
  AlertDialogCancel,
}

Other notes

This should be connected to the RFC of the general Dialog once it is created

Can't open a dialog from a different component.

Please provide the environment you discovered this bug in.

@angular/core: "16.2.6"
@angular/cdk: "16.2.8"
@spartan-ng/ui-core: "0.0.1-alpha.309"
@spartan-ng/ui-dialog-brain: "0.0.1-alpha.309"

Which area/package is the issue in?

dialog

Description

Currently the Angular's cdk dialog login is wrapped inside brnDialog (and brnDialogService), so it's impossible to trigger open a dialog from another component.

Say I want to open a dialog from a random button. With Angular's cdk I would do:

import {Component, inject} from "@angular/core";
import {Dialog} from "@angular/cdk/dialog";

@Component({
    selector: 'app-button',
    standalone: true,
    template: `
    <button (click)="openDialog()">Open</button>`
})
export class ButtonComponent {
    dialog = inject(Dialog);
    
    openDialog() {
        this.dialog.open(MyDialogComponent);
    }
}

BrnDialogService is made to be use only inside BrnDialog, so it removes this feature completely.
The only way to show a dialog is to have both dialog and trigger in the same template,

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Aspect Ratio

Avatar

Assesment

Time Complexity Risk Description
2 2 1 Just some CSS magic with absolute and relative positioning

Dependencies

None

brn


API (proposed)

shadcn simply reexports radix. Radix uses what I would say is the equivalent to Angular's content projection. I'd just use that.

<brn-aspect-ratio [ratio]="3/4">
   <img alt="bla" src="bla"/>
</brn-aspect-ratio>

Proposed inputs

Input Type Default Description
ratio number 1 Aspect ratio of the projected content

hlm


Reexport brn.

Aria


Roles, States & Properties

DNA

Radix


API

import React from 'react';
import * as AspectRatio from '@radix-ui/react-aspect-ratio';
import './styles.css';

const AspectRatioDemo = () => (
  <div className="Container">
    <AspectRatio.Root ratio={16 / 9}>
      <img
        className="Image"
        src="https://images.unsplash.com/photo-1535025183041-0991a977e25b?w=300&dpr=2&q=80"
        alt="Landscape photograph by Tobias Tullius"
      />
    </AspectRatio.Root>
  </div>
);

export default AspectRatioDemo;

Source

https://www.radix-ui.com/docs/primitives/components/aspect-ratio

#shadcn


API

import Image from "next/image"
 
import { AspectRatio } from "@/components/ui/aspect-ratio"
 
export function AspectRatioDemo() {
  return (
    <AspectRatio ratio={16 / 9} className="bg-muted">
      <Image
        src="https://images.unsplash.com/photo-1588345921523-c2dcdb7f1dcd?w=800&dpr=2&q=80"
        alt="Photo by Drew Beamer"
        fill
        className="rounded-md object-cover"
      />
    </AspectRatio>
  )
}

Source

"use client"
 
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
 
const AspectRatio = AspectRatioPrimitive.Root
 
export { AspectRatio }

https://ui.shadcn.com/docs/components/aspect-ratio

Nx plugin is not installing peer dependencies correctly

Please provide the environment you discovered this bug in.

Nx plugin alpha

Which area/package is the issue in?

Don't know / other

Description

Currently if you add a component with the Nx plugin updates the package.json correctly.

It also seems to run an npm install, however during the install, peer dependencies are not added.

Example:
npx nx g @spartan-ng/ui button

ui-core is installed as a dependency
ui-core has class-variance-authority and clsx as peer dependencies.

ui-core is installed but the peer dependencies are not

Please provide the exception or error you saw

No response

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

cannot disable button programatically in brmMenu -> brmMenuGroup -> brnMenuItem

Please provide the environment you discovered this bug in.

<button type="button" hlmBtn size="sm" variant="ghost" [brnMenuTriggerFor]="pipelineMenu">
  <hlm-icon name="radixDotsHorizontal" size="sm" />
</button>
<ng-template #pipelineMenu>
  <div hlm brnMenu class="w-56">
    <div brnMenuGroup>
      <button brnMenuItem [disabled]="true"> <!-- HERE -->
        <hlm-icon name="radixCross2" hlmMenuIcon />
        <span>Delete Pipeline</span>
        <hlm-menu-shortcut>โŒ˜D</hlm-menu-shortcut>
      </button>
    </div>
  </div>
</ng-template>

Which area/package is the issue in?

dropdown-menu

Description

Hi, first of all - thanks for your amazing work! We are already using some of the components in production environment no matter this components are still in alpha ;-)

As I provided example above, I cannot disable button with brnMenuItem directive applied programatically using the [disabled]="true" for example. Note that if I use just disabled html property it works as expected; like that:

<button brnMenuItem disabled></button> <!-- this works -->

but not:

<button brnMenuItem [disabled]=""true"></button> <!-- this doesn't work -->

This is not a blocker, I use a workaround to display disabled button conditionally with *ngIf, but need to render same button twice.

Please provide the exception or error you saw

No exception or error is thrown for this behavior, it's also not expected.

Other information

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Carousel

Which scope/s are relevant/related to the feature request?

Don't know / other

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

RFC: Slider

Which scope/s are relevant/related to the feature request?

slider

Information

TBD

Describe any alternatives/workarounds you're currently using

No response

I would be willing to submit a PR to fix this issue

  • Yes
  • No

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    ๐Ÿ–– Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. ๐Ÿ“Š๐Ÿ“ˆ๐ŸŽ‰

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google โค๏ธ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.