goetzrobin / spartan Goto Github PK
View Code? Open in Web Editor NEWCutting-edge tools powering Angular full-stack development.
Home Page: https://spartan.ng
License: MIT License
Cutting-edge tools powering Angular full-stack development.
Home Page: https://spartan.ng
License: MIT License
@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"
dialog
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,
No response
No response
Time | Complexity | Risk | Description |
---|---|---|---|
2 | 3 | 0 | Some styles, some logic, shouldn't be very hard :) |
None
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";
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 |
Already implemented :) I'd just compose it with the proposed brn directive.
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.
N/A
import { Badge } from "@/components/ui/badge";
<Badge variant="outline">Badge</Badge>;
Latest version 0.0.1-alpha.318
checkbox
missing peerdependencies for checkbox component
No response
No response
Time | Complexity | Risk | Description |
---|---|---|---|
2 | 2 | 1 | Just some CSS magic with absolute and relative positioning |
None
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>
Input | Type | Default | Description |
---|---|---|---|
ratio | number | 1 | Aspect ratio of the projected content |
Reexport brn.
DNA
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;
https://www.radix-ui.com/docs/primitives/components/aspect-ratio
#shadcn
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>
)
}
"use client"
import * as AspectRatioPrimitive from "@radix-ui/react-aspect-ratio"
const AspectRatio = AspectRatioPrimitive.Root
export { AspectRatio }
Don't know / other
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}`;
}
}
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>
No response
In prod on https://www.spartan.ng/components/button loading section.
button
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
No response
No response
Don't know / other
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?
No response
After a fresh install and importing the HlmSheetModule the HlmSheetHeaderComponent could not be resolved.
sheet
After a fresh install and importing the HlmSheetModule the HlmSheetHeaderComponent could not be resolved.
No response
No response
Don't know / other
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.
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.
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>
brnMenuItem
directive on the host element, it won't apply the a11y logic to the right element (the button)brnMenuItem
directive to the button instead, it would no longer be a direct child of brnMenu and wouldn't work.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.
asChild
feature of RadixHave 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.
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.
No response
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!
accordion
Component doesn't open on click event
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)
No response
Don't know / other
TBD
No response
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>
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 |
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.
role="tooltip"
and aria-hidden="true"
by default.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)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.esc
keypress.focus
and hover
events to cover keyboard & mouse users.
hover
it should be dismissed on mouseleave
or esc
keypress.focus
it should be dismissed on blur
or esc
keypress.
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.https://www.w3.org/WAI/ARIA/apg/patterns/tooltip/
w3c/aria-practices#128
https://inclusive-components.design/tooltips-toggletips/
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;
https://www.radix-ui.com/docs/primitives/components/tooltip
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>
);
}
checkbox
Time | Complexity | Risk | Description |
---|---|---|---|
3 | 2 | 0 | Mostly styles |
None
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>
Input | Type | Default | Description |
---|---|---|---|
readonly | BooleanInput | false | toggles the readonly state of the checkbox |
The hlmCheckbox directive would be composed with the brnCheckbox & apply the appropriate styles.
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)
const CheckboxDemo = () => (
<Checkbox.Root
defaultChecked
id="c1"
>
<Checkbox.Indicator>
<CheckIcon />
</Checkbox.Indicator>
</Checkbox.Root>
<label htmlFor="c1">
Accept terms and conditions.
</label>
);
https://www.radix-ui.com/docs/primitives/components/checkbox
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>
)
}
https://ui.shadcn.com/docs/components/checkbox
No response
Missing exportAs inside brnTabs
tabs
The issue occurs when I try to create a custom component and trying to access brnTabs value.
No response
No response
It shows 404 not found.
navigation-menu
TBD
No response
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.
dialog
Missing hlm-dialog.component.ts and hlm-dialog-content.component.ts at install. Same for alertdialog.
Does not match the documentation.
No response
P.S. : thank you for creating this. โค๏ธ
Do not have time to contribute right now, but 300 have not been reached yet... ๐
Blank new project using Spartan UI and tailwind
Don't know / other
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
No response
Clean install of the repo.
sheet
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 :)
Log the `open` method in `BrnSheetTriggerDirective` and see it triggering twice.
No response
Don't know / other
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:
Another idea is to add a docker-compose.yml file that spins up a local Postgres DB?
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.
pagination
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,
}
https://ui.shadcn.com/docs/components/pagination
Since we rely on html tags here to drive accessibility we should probably make most of the styling come from directives
No response
<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>
dropdown-menu
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.
No exception or error is thrown for this behavior, it's also not expected.
No response
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:
npm run prettify
as a pre-commit hook locally. - This is straightforward.Ubuntu 22.04.3 LTS
Node Version - v18.14.2
Docs
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
No response
No response
Ubuntu 22.04.3 LTS
Node Version - v18.14.2
Don't know / other
Getting the below error:
Step 1 - run yarn dev
Step 2- Goto http://127.0.0.1:4200/
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
No response
In prod on https://www.spartan.ng/components/avatar loading section.
avatar
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
No response
No response
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"
}
}
command and popover
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.
No response
No response
As stated in the angular.dev docs:
Always prefer using the host property over @HostBinding and @HostListener. These decorators exist exclusively for backwards compatibility.
Im gonna tackle this.
Don't know / other
Pretty set on using ng-signal-forms
here, but open to explore other options that ideally are signal driven!
No response
spartan 0.0.1-alpha.313
"@angular/core": "17.0.2",
accordion
sheet
dialog
alert-dialog
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.
In this case the padding is added twice wich is not expected.
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.
Don't know / other
TBD
No response
There is an issue with import {NgScrollbarModule} from 'ngx-scrollbar';
scroll-area
on my project I have exactly the same problem with the width.
The case is to switch between code and preview, the result is:
No response
No response
date-picker
TBD
No response
calendar
TBD
No response
<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?
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-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
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>
);
https://github.com/radix-ui/primitives/tree/main/packages/react/alert-dialog/src
https://www.w3.org/WAI/ARIA/apg/patterns/alertdialog/
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.
Note
<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>
"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,
}
This should be connected to the RFC of the general Dialog once it is created
New/Pure nx workspace installation with all components.
dropdown-menu
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.
No response
No response
Created an NX based AnalogJS project with the defaults and added the UI components as described in the docs.s
Don't know / other
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)
โ 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)
No response
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 |
None
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>+{{users.length - 5}}</span>
</brn-avatar>
</brn-avatars>
Input | Type | Default | Description |
---|---|---|---|
autoColor | string/undefined | undefined | Auto-gens a background color & text-contrast color based on provided text content |
Input | Type | Default | Description |
---|---|---|---|
max | number/ undefined | undefined | The maximum number of avatars to display (if more they get displayed as the brnAvatarsMax component) |
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.
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.
import * as Avatar from "@radix-ui/react-avatar";
export default () => (
<Avatar.Root>
<Avatar.Image />
<Avatar.Fallback />
</Avatar.Root>
);
https://www.radix-ui.com/docs/primitives/components/avatar
#shadcn
import { Avatar, AvatarFallback, AvatarImage } from "@/components/ui/avatar";
<Avatar>
<AvatarImage src="https://github.com/shadcn.png" />
<AvatarFallback>CN</AvatarFallback>
</Avatar>;
select
<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>
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.
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.
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.
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
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
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 |
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
Can probably just have a a mouseenter hostbinding to trigger and change the option focus on cdkList
same as number 7
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>>();
We can have one for each brn component/directive to add associated styles with an addition to these
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
we can generate id's for the select trigger and select content elements and provide controls and controlledBy + LabelledBy respectively.
Aria expanded on select-trigger and role of combobox
cdkList is taking care of all the Listbox ADA and keyboard navigation so no need to worry much about that.
<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>
https://www.radix-ui.com/primitives/docs/components/select
<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>
https://ui.shadcn.com/docs/components/select
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.
No response
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.
alert
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.
No response
No response
slider
TBD
No response
collapsible
Time | Complexity | Risk | Description |
---|---|---|---|
3 | 2 | 0 | Refactor Accordion to extract collapsible |
Becomes dependency of accordion
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>
Input | Type | Default | Description |
---|---|---|---|
open | BooleanInput | false | allows to programmatically change if collapsible is open |
This is a brain only component.
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.export default () => (
<Collapsible.Root>
<Collapsible.Trigger />
<Collapsible.Content />
</Collapsible.Root>
);
https://www.radix-ui.com/docs/primitives/components/collapsible
export default () => (
<Collapsible.Root>
<Collapsible.Trigger />
<Collapsible.Content />
</Collapsible.Root>
);
https://ui.shadcn.com/docs/components/collapsible
Does not exist yet.
The "Framework" dropdown in the first "Preview" tab in https://www.spartan.ng/components/card
Seen in Firefox & Microsoft Edge, on Windows 11.
select
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.
_No response_
Working in light mode:
Not working in dark mode:
Docs
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.
N/A
toast
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...
No response
@angular/core: "16.2.6"
@spartan-ng/ui-core: "0.0.1-alpha.309"
@spartan-ng/ui-avatar-brain: "0.0.1-alpha.309"
avatar
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
.
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 :(
No response
This may happen in other primitives that uses signals as classes like Aspect Ratio and Icon, but I haven't tested it.
Nx plugin alpha
Don't know / other
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
No response
No response
"@angular/cdk": "^17.0.0",
"@angular/core": "~17.0.2",
"@spartan-ng/ui-core": "^0.0.1-alpha.309",
Icon
When using OnPush on the Parent Component the size is not working, because the Hostbinding is not refreshed.
The Size of the icon is always the default size.
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
Locally, after installing via nx-create-workspace
Don't know / other
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.
No response
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...
A declarative, efficient, and flexible JavaScript library for building user interfaces.
๐ Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
An Open Source Machine Learning Framework for Everyone
The Web framework for perfectionists with deadlines.
A PHP framework for web artisans
Bring data to life with SVG, Canvas and HTML. ๐๐๐
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
Some thing interesting about web. New door for the world.
A server is a program made to process requests and deliver data to clients.
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
Some thing interesting about visualization, use data art
Some thing interesting about game, make everyone happy.
We are working to build community through open source technology. NB: members must have two-factor auth.
Open source projects and samples from Microsoft.
Google โค๏ธ Open Source for everyone.
Alibaba Open Source for everyone
Data-Driven Documents codes.
China tencent open source team.