Archive

Archive for July, 2020

Create A Responsive Dashboard With Angular Material And ng2-Charts

July 27th, 2020 No comments
Navigation component

Create A Responsive Dashboard With Angular Material And ng2-Charts

Create A Responsive Dashboard With Angular Material And ng2-Charts

Zara Cooper

2020-07-27T10:00:00+00:00
2020-07-27T15:35:48+00:00

Creating a dashboard from scratch is often pretty complicated. You have to create tools to collect data on items of interest. Once collected this data has to be presented in an easy to understand and meaningful way to your users. It involves intricate planning of what data to include and how to display it effectively. Once you have a plan, implementing the design is a massive task especially since it involves building multiple components.

With Angular Material and ng2-charts, you can take advantage of schematics to cut down the effort and time you may spend building a dashboard. Angular Material ships with a number of schematics that you could use to generate a dashboard. Similarly, ng2-charts provides schematics for generating multiple chart components. In this article, I’ll illustrate how to use both ng2-charts and Angular Material to set up a dashboard fairly quickly.

An Example

To illustrate how to build a dashboard, we’ll take the example of an online store selling leather goods like bags, wallets, key holders, and so on. The store owner would like to track information such as where customers come from to their online store, how their products sell, how traffic sources relate to sales, among other things.

We’ll build a dashboard to display this information and help the store owner analyze it. The dashboard will contain four small summary cards, four different kinds of charts, and a table listing most recent orders made. The four summary cards will display information such as total revenue from sales, average order value, the total number of orders, and number of returning customers. The charts will display the number of units sold for each product, sales by traffic source, online store sessions over time, and sales for the week.

Prerequisites

To follow along, you’ll need to have Angular CLI installed. If you do not have it installed, you can find out how to get it at cli.angular.io. If you’re not starting from a pre-existing Angular project, you need to generate one by running ng new . For instance, to create an admin panel for the aforementioned store, we’ll run:

ng new store-admin-panel

Your project also needs to have routes configured for it. If you’re starting from a new app, select yes when prompted on whether to add an Angular Routing module during your project setup above.

Add Angular Material And Ng2-Charts To Your Project

Angular Material ships with various schematics for generating a variety of useful components like address books, trees, tables, navigation, and so on. To add Angular Material to your project, run:

ng add @angular/material

Pick a theme from the options provided in subsequent prompts. Next, you’ll be prompted to choose whether to add Angular Material typography styles and browser animations. You do not need these and could just respond no.

Next, you’ll need to install ng2-charts. ng2-charts requires charts.js as a dependency. To install ng2-charts run:

npm install ng2-charts --save

Then install charts.js:

npm install chart.js --save

To access the charts, add the ChartsModule to the AppModule‘s imports.

import { ChartsModule } from 'ng2-charts';

@NgModule({
  imports: [
   …
   ChartsModule,
   …
]
})

Lastly, install ng2-charts schematics as a dev dependency because they do not ship with ng2-charts by default.

npm install --save-dev ng2-charts-schematics

Generating A Navigation Component

First off, we’ll need to add a navigation component to help users maneuver through the app comfortably. The navigation should contain links to the dashboard and other pages that will be part of the admin panel. Angular material provides a schematic that generates a navigation component. We’ll name this component nav. Adding a side nav to the application is accomplished by running:

ng generate @angular/material:navigation nav

To link other routes in the navigation, use the routerLink directive and change the page name in the toolbar depending on what route a user is on.

// nav.component.ts
...
menuItems = ['dashboard', 'sales', 'orders', 'customers', 'products'];
<!--nav.component.html-->
...
<mat-nav-list>
     <a *ngFor="let item of menuItems" mat-list-item [routerLink]="'/'+item"> {{item | titlecase}} </a>
...

To see this component, add it to app.component.html.

<!--app.component.html-->
<app-nav></app-nav>

This is what the NavComponent looks like.

Navigation component

Navigation component (Large preview)

Since the nav will be displayed alongside other components, adding a router-outlet to it would help switch between the other different components. In the nav.component.html template, just after the closing , replace the comment with .

<!--nav.component.html-->
<mat-sidenav-container>
   ...
   <mat-sidenav-content>
     <mat-toolbar>
       ...
     </mat-toolbar>
     <router-outlet></router-outlet>
   </mat-sidenav-content>
 </mat-sidenav-container>

In the screenshots that follow in this article, this nav component will be omitted to better highlight the dashboard we’ll be generating for the sake of the tutorial. If you’re following along while building this dashboard, the nav will still appear as pictured above in your browser with the dashboard within it.

Generate The Dashboard

The most important part of the dashboard is its layout. It needs to hold all the components mentioned earlier and be responsive when displayed on different devices. To generate the dashboard layout, you’ll need to run the @angular/material:dashboard schematic. It will generate a responsive dashboard component. Pass the preferred name for your dashboard to the schematic. In this instance, let’s name it dash.

ng generate @angular/material:dashboard dash

To view the newly generated dashboard within the nav component, add a route for it to the router.

// app-routing.module.ts
import { DashComponent } from './dash/dash.component';

const routes: Routes = [{ path: 'dashboard', component: DashComponent }];

@NgModule({
  imports: [RouterModule.forRoot(routes)],
  exports: [RouterModule]
})

Once done, to see the results, run npm start and head on over to localhost:4200/dashboard. You should see this:

generated dashboard component

Generated dashboard component (Large preview)

The schematic generates four cards in the template and displays them in a responsive grid. The Angular Material CDK uses the Layout package to style this responsive card grid. The BreakpointObserver utility of the Layout package assesses media queries and makes UI changes based on them. There are various breakpoints available but within the generated component, only two categories are catered for. The Breakpoints.Handset and other queries that do not match it. The Layout package specifies 14 breakpoint states that you can use to customize the responsiveness of your dashboard.

// dashboard.component.js
...
cards = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
    map(({ matches }) => {
      if (matches) {
        ...
      }
      ...
    })
  );

Going back to the dashboard, since four summary cards, four charts, and a table will be on the dashboard, we need nine cards in total. Breakpoints.Handset and Breakpoints.Tablet matches will display in a one-column grid where:

  • The four summary cards will span one row.
  • The charts will span two rows.
  • The table will span four rows.

Non-Breakpoints.Handset and non-Breakpoints.Tablet matches will display in four columns where:

  • The four summary cards will span one row and one column.
  • The charts will span two rows and two columns.
  • The table will span four rows and four columns.

It should look something like the screenshot below in non-Breakpoints.Handset and non-Breakpoints.Tablet matches. On Breakpoints.Handset and Breakpoints.Tablet matches, everything will just display in one column.

dashboard component with additional cards

Dashboard component with additional cards (Large preview)

Create A Card Component

In the dashboard component, all the cards are generated through iteration. To prevent repetition, when adding all the new components, we’ll create a reusable card component. The card component will accept a title as input and use ng-content to dynamically add the rest of the content. To create the card component, run:

ng g c card -m app --style css

From the dashboard component template, we’ll just take the markup enclosed within the tag and place it In the card template:

<!--card.component.html-->
<mat-card class="dashboard-card">
   <mat-card-header>
       <mat-card-title>
           {{title}}
           <button mat-icon-button class="more-button" [matMenuTriggerFor]="menu" aria-label="Toggle menu">
               <mat-icon>more_vert</mat-icon>
           </button>
           <mat-menu #menu="matMenu" xPosition="before">
               <button mat-menu-item>Expand</button>
               <button mat-menu-item>Remove</button>
           </mat-menu>
       </mat-card-title>
   </mat-card-header>
   <mat-card-content class="dashboard-card-content">
       <ng-content></ng-content>
   </mat-card-content>
</mat-card>

To add the title as input to the card:

// card.component.ts
import { Component, Input } from '@angular/core';

...

export class CardComponent{
 @Input() title: string;


...
}

To style the card:

/*card.component.css*/
.more-button {
   position: absolute;
   top: 5px;
   right: 10px;
}

.dashboard-card {
   position: absolute;
   top: 15px;
   left: 15px;
   right: 15px;
   bottom: 15px;
}


.dashboard-card-content {
   text-align: center;
   flex-grow: 1;
   display: flex;
   flex-direction: column;
   align-items: center;
   max-height: 100%;
   justify-content: center;
   align-items: stretch;
}

mat-card {
   display: flex;
   flex-direction: column;
}

Adding Cards To The Dashboard

Since the dashboard elements will be added individually and not through iteration, the dashboard component needs to be modified to account for this. In dashboard.component.ts, remove the cards property and replace it with a cardLayout property instead. The cardLayout variable will define the number of columns for the material grid list and how many rows and columns each of the dashboard cards will span. Breakpoints.Handset and Breakpoints.Tablet query matches will display in 1 column and those that do not match will display in 4 columns.

// dashboard.component.js
...


cardLayout = this.breakpointObserver.observe(Breakpoints.Handset).pipe(
   map(({ matches }) => {
     if (matches) {
       return {
         columns: 1,
         miniCard: { cols: 1, rows: 1 },
         chart: { cols: 1, rows: 2 },
         table: { cols: 1, rows: 4 },
       };
     }

    return {
       columns: 4,
       miniCard: { cols: 1, rows: 1 },
       chart: { cols: 2, rows: 2 },
       table: { cols: 4, rows: 4 },
     };
   })
 );

...

In the dash.component.html template, replace the colspan and rowspan values of mat-grid-tile elements and the cols property of the mat-grid-list element.

<!--dash.component.html-->
<div class="grid-container">
 <h1 class="mat-h1">Dashboard</h1>
 <mat-grid-list cols="{{ ( cardLayout | async )?.columns }}" rowHeight="200px">
   <!--Mini Cards-->
   <mat-grid-tile *ngFor="let i of [1, 2, 3, 4]" [colspan]="( cardLayout | async )?.miniCard.cols"
     [rowspan]="( cardLayout | async )?.miniCard.rows">
     <app-card title="Card {{i}}"><div>Mini Card Content Here</div></app-card>
   </mat-grid-tile>
   <!--Charts-->
   <mat-grid-tile *ngFor="let i of [5, 6, 7, 8]" [colspan]="( cardLayout | async )?.chart.cols"
     [rowspan]="( cardLayout | async )?.chart.rows">
     <app-card title="Card {{i}}"><div>Chart Content Here</div></app-card>
   </mat-grid-tile>
   <!--Table-->
   <mat-grid-tile [colspan]="( cardLayout | async )?.table.cols" [rowspan]="( cardLayout | async )?.table.rows">
     <app-card title="Card 9"><div>Table Content Here</div></app-card>
   </mat-grid-tile>
 </mat-grid-list>
</div>

The dashboard will end up looking exactly like the most recent screenshot linked above.

Generating The Charts

The four charts that we need for the dashboard are:

  • A radar chart of products by unit sold.
  • A pie chart of sales by traffic source.
  • A bar chart of online store sessions.
  • A line chart of sales across the year.

Similar to creating the dashboard, generating chart components involves running a schematic. Using the ng2-charts schematics, generate the four different charts. We’ll place them in a folder called charts. Run ng generate ng2-charts-schematics: .

ng generate ng2-charts-schematics:radar charts/product-sales-chart
ng generate ng2-charts-schematics:pie charts/sales-traffic-chart
ng generate ng2-charts-schematics:line charts/annual-sales-chart 
ng generate ng2-charts-schematics:bar charts/store-sessions-chart

After running these commands, all four chart components are generated and are populated with sample data ready for display. Depending on what data you’d like to show, pick charts that most suit your data visualization needs. For each of the charts generated above, add the chartContainer class to the divs that enclose the canvas element in the chart templates.

<div class="chartContainer">
  <canvas baseChart width="400" height="400">
    ...

Next, add this styling to styles.css so that they could be accessible to all the chart components.

/*styles.css*/
...

.chartContainer canvas {
    max-height: 250px;
    width: auto;
}

.chartContainer{
    height: 100%;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
}

Adding Data To The Charts

The generated chart components come with sample data already plugged in. If you have pre-existing services that provide your own data, you can add this data from them to the chart components. The charts take labels for the x-axis, data or data sets, a chart type, colors, a legend as well as other customization options. To provide the data and labels to the charts, create a service that will fetch data from a source of your choice and return it in a form that the charts accept. For instance, the AnnualSalesChartComponent receives its dataset and labels from the SalesService‘s getSalesByMonth method which returns an array of sales for each month for the current year. You can find this service here and data it returns here. Inject the service as a private property to the AnnualSalesChartComponent constructor. Call the method that returns the required chart data from the service within the ngOnInit lifecycle hook.

// annual-sales-chart.component.ts
import { SalesService } from 'src/app/sales/sales.service';


...


export class AnnualSalesChartComponent implements OnInit {
  public salesChartData: ChartDataSets[] = [
   { data: [], label: 'Total Sales' },
 ];

 public salesChartLabels: Label[] = [];
  ...

  constructor(private salesService: SalesService) { }
  ngOnInit() {
   this.salesService.getSalesByMonth().subscribe({
     next: salesItems => {
       salesItems.forEach(li => {
         this.salesChartData[0].data.push(li.revenue);
         this.salesChartLabels.push(li.month);
       });
     },
    ...
   });
 }
}

Adding Charts To The Dashboard

The next step involves adding the charts to the dashboard, in dash.component.html. Here’s what that looks like:

<!--dash.component.html-->
...

<!--Charts-->
<mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
   <app-card title="Monthly Revenue">
       <app-annual-sale-chart></app-annual-sale-chart>
   </app-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
   <app-card title="Product Sales">
       <app-product-sales-chart></app-product-sales-chart>
   </app-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
   <app-card title="Sales by Traffic Source">
       <app-sales-traffic-chart></app-sales-traffic-chart>
   </app-card>
</mat-grid-tile>
<mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
   <app-card title="Online Store Sessions by Traffic Source">
       <app-store-sessions-chart></app-store-sessions-chart>
   </app-card>
</mat-grid-tile>

...

This is what the resultant responsive dashboard looks like.

dashboard with charts

Dashboard with charts (Large preview)

Generating A Table

We’ll add an orders table to give the shop owner an overview of the most recent orders placed and their status. To generate the orders table component, run the schematic:

ng generate @angular/material:table orders-table

This will generate a table component that will look like this.

table generated by Angular Material schematic

Table generated by Angular Material schematic (Large preview)

Tables with many columns may be difficult to make responsive for handset and tablet views. When adding the table to a card, make it horizontally scrollable so that all the data can be viewed properly and is not obstructed. You can do this by adding the styling below to your table component:

<!--table.component.html-->
<div class="mat-elevation-z8 small-table">
 <table mat-table class="full-width-table" matSort aria-label="Elements">
   ...
/*table.component.css*/

...

.small-table{
  overflow-x: scroll !important;
}

To add the table to the dash component:

<!-- dashboard.component.html>
...
  <mat-grid-tile [colspan]="( cardLayout | async )?.table.cols" [rowspan]="( cardLayout | async )?.table.rows">
     <app-card title="Latest Orders">
       <app-orders-table></app-orders-table>
     </app-card>
  </mat-grid-tile>
...

Adding Data To The Table

Like with charts, you can add data to the table in the ngOnInit method from a service. Additionally, you will need to modify your table’s generated data source to consume data from the service. To start off, inject the service in the table’s class constructor. Let’s take the example of a table listing the latest orders for this dashboard. To get data for the table, let’s inject the OrderService in the OrdersTableComponent constructor, change the MatTable type assertion of the table view child, and amend the list of displayed columns to reflect an order interface. If you’re interested in the data being added to the table, you can find it here. The last thing involves getting the total length of the data items available to be used to set the total in the table’s .

// orders-table.component.ts
import { OrderService } from '../orders.service';
import { Order } from '../order';
...
export class OrdersTableComponent implements AfterViewInit, OnInit {
...
 @ViewChild(MatTable) table: MatTable;
 dataLength: number;
 
 displayedColumns = [
   "id",
   "date",
   "name",
   "status",
   "orderTotal",
   "paymentMode",
 ];
...
constructor(private orderService: OrderService){}

ngOnInit() {
 this.datasource = new OrdersTableDataSource(this.orderService);
 this.orderService.getOrderCount().subscribe({
   next: orderCount => {
    this.dataLength = orderCount;
   },
   ...
 });
}
...
}

Next, we’ll need to modify the OrdersTableDataSource class to accept the OrderService as a parameter in its constructor. We’ll have to modify its connect and destroy methods as well. The connect method connects the data source to the table and updates the table when new data items are emitted from the stream it returns, in this case, an orders array observable. The dataMutations constant combines the first data load, pagination, and sorting events into one stream for the table to consume. Pagination and sorting are handled by the OrderService server-side. So we need to pass the offset and page size from paginator and the active sort field and sort direction of the sort property to the getOrders method of the OrderService. The disconnect method should be used to close any connections made and release resources held up in the connect method.

// orders-table.datasource.ts
...
export class OrdersTableDataSource extends DataSource<Order> {
 paginator: MatPaginator;
 sort: MatSort;

 constructor(private orderService: OrderService) {
   super();
 }

 connect(): Observable<Order[]> {
   const dataMutations = [
     of('Initial load'),
     this.paginator.page,
     this.sort.sortChange
   ];

   return merge(...dataMutations).pipe(mergeMap(() => {
     return this.orderService.getOrders(
       this.paginator.pageIndex * this.paginator.pageSize,
       this.paginator.pageSize,
       this.sort.active,
       this.sort.direction
     );
   }));
 }

 disconnect() {} 
}

In the orders table template, insert the new columns and bind the length property of to the dataLength property. For the status column, use a element for better visualization of the order status. To have access to , add the MatChipsModule as an import to AppModule.

<!-- orders-table.component.html -->
<div class="mat-elevation-z8">
 <table mat-table class="full-width-table" matSort aria-label="Elements">
   <!-- Id Column -->
   <ng-container matColumnDef="id">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Id</th>
     <td mat-cell *matCellDef="let row">{{row.id}}</td>
   </ng-container>

   <!-- Date Column -->
   <ng-container matColumnDef="date">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Date</th>
     <td mat-cell *matCellDef="let row">{{row.date | date }}</td>
   </ng-container>

   <!-- Name Column -->
   <ng-container matColumnDef="name">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Name</th>
     <td mat-cell *matCellDef="let row">{{row.name}}</td>
   </ng-container>

   <!-- Order Total Column -->
   <ng-container matColumnDef="orderTotal">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Order Total</th>
     <td mat-cell *matCellDef="let row">{{row.orderTotal | currency}}</td>
   </ng-container>

   <!-- Payment Mode Column -->
   <ng-container matColumnDef="paymentMode">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Payment Mode</th>
     <td mat-cell *matCellDef="let row">{{row.paymentMode}}</td>
   </ng-container>

   <!-- Status Column -->
   <ng-container matColumnDef="status">
     <th mat-header-cell *matHeaderCellDef mat-sort-header>Status</th>
     <td mat-cell *matCellDef="let row">
       <mat-chip-list>
         <mat-chip color="{{ row.status == 'delivered' ? 'primary' : ( row.status == 'shipped' ? 'accent' : 'warn' ) }}" selected>
           {{row.status}}
         </mat-chip>
       </mat-chip-list>
     </td>
   </ng-container>

   <tr mat-header-row *matHeaderRowDef="displayedColumns"></tr>
   <tr mat-row *matRowDef="let row; columns: displayedColumns;"></tr>
 </table>

 <mat-paginator #paginator [length]="dataLength" [pageIndex]="0" [pageSize]="5" [pageSizeOptions]="[5, 10, 15, 20]">
 </mat-paginator>
</div>

Once data has been added to the table, this is what the dashboard will look like:

dashboard with charts and table

Dashboard with charts and table (Large preview)

Creating A Mini Card Component

All that’s left to complete the dashboard is to populate the four small cards that sit at the top. Having smaller summary cards as part of the dashboard makes it easy to highlight brief pieces of information that do not need whole charts or tables. In this example, the four mini cards will display total sales, average order value, the total number of orders, and the number of returning customers that visited the store for the day. This is just an example. These mini cards cannot be generated like with the navigation, dashboard layout, charts, and the table. They have no schematics. Below we’ll briefly go through how to create them. Although we’re going to add data specific to the example, you can add whatever you want to them or decide to do away with them altogether. To start off, generate the mini-card component, run:

ng g c mini-card -m app --style css

You can find the template for the component linked here and its styling here. This component has eight input properties that you can find out how to add here. To get data to the mini card components, inject the service that provides data to them in the DashComponent constructor. Assign data received from the service to a property of the DashComponent. In this instance, we’ll get data from the StoreSummaryService and assign it to the miniCardData property. Here’s how:

// dash.component.ts
export class DashComponent implements OnInit{
...
 miniCardData: StoreSummary[];

 constructor(private breakpointObserver: BreakpointObserver, private summaryService: StoreSummaryService) {}

 ngOnInit() {
   this.summaryService.getStoreSummary().subscribe({
     next: summaryData => {
       this.miniCardData = summaryData;
     }
   });
 } 
}

To add the mini-cards to the dash component and have them populated with data from the service:

<!--dash.component.html-->
...

<!--Mini Cards-->
   <mat-grid-tile *ngFor="let mc of miniCardData" [colspan]="( cardLayout | async )?.miniCard.cols"
     [rowspan]="( cardLayout | async )?.miniCard.rows">
     <app-mini-card [title]="mc.title" [textValue]="mc.textValue" [value]="mc.value" [color]="mc.color" [percentValue]="mc.percentValue"></app-mini-card>
   </mat-grid-tile>

...

The screenshot below is what the dashboard will look like with the mini cards populated.

dashboard with charts, tables, and mini-cards

Dashboard with charts, tables, and mini-cards. (Large preview)

Putting All Together

In the end, the dashboard component template should contain:

<!-- dashboard.component.html -->
<div class="grid-container">
 <h1 class="mat-h1">Dashboard</h1>
 <mat-grid-list cols="{{ ( cardLayout | async )?.columns }}" rowHeight="200px">
   <!--Mini Cards-->
   <mat-grid-tile *ngFor="let mc of miniCardData" [colspan]="( cardLayout | async )?.miniCard.cols"
     [rowspan]="( cardLayout | async )?.miniCard.rows">
     <app-mini-card [icon]="mc.icon" [title]="mc.title" [value]="mc.value" [color]="mc.color" [isIncrease]="mc.isIncrease" duration="since last month" [percentValue]="mc.percentValue" [isCurrency]="mc. isCurrency"></app-mini-card>
   </mat-grid-tile>
   <!--Charts-->
   <mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
     <app-card title="Monthly Revenue">
       <app-annual-sale-chart></app-annual-sale-chart>
     </app-card>
   </mat-grid-tile>
   <mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
     <app-card title="Product Sales">
       <app-product-sales-chart></app-product-sales-chart>
     </app-card>
   </mat-grid-tile>
   <mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
     <app-card title="Sales by Traffic Source">
       <app-sales-traffic-chart></app-sales-traffic-chart>
     </app-card>
   </mat-grid-tile>
   <mat-grid-tile [colspan]="( cardLayout | async )?.chart.cols" [rowspan]="( cardLayout | async )?.chart.rows">
     <app-card title="Online Store Sessions by Traffic Source">
       <app-store-sessions-chart></app-store-sessions-chart>
     </app-card>
   </mat-grid-tile>
   <!--Table-->
   <mat-grid-tile [colspan]="( cardLayout | async )?.table.cols" [rowspan]="( cardLayout | async )?.table.rows">
     <app-card title="Latest Orders">
       <app-orders-table></app-orders-table>
     </app-card>
   </mat-grid-tile>
 </mat-grid-list>
</div>

Here’s what the resultant dashboard contains.

completed dashboard

Completed dashboard (Large preview)

Conclusion

Creating dashboards involves a fair amount of work and planning. A way to make building them faster is to use the various schematics provided by Angular Material and ng2-charts. With these schematics, running a command will generate a wholly complete component and can result in having a dashboard up and running fairly quickly. This leaves you a lot more time to focus on creating data services and adding them to your dashboard components.

If you want to learn more about some of the schematics provided by Angular Material, visit material.angular.io, and for those provided by ng2-charts, visit their site linked here.

(ra, yk, il)

Categories: Others Tags:

How Freelance Designers Can Work Safely

July 27th, 2020 No comments

Even as many countries start to emerge from pandemic-induced lockdown, it’s unlikely that the business world as a whole will ever return in full force to the traditional office structure. It’s been made abundantly clear that people can — and do — work just as productively from afar as they do when asked to work alongside their colleagues under controlled conditions.

Couple this with the still-rising acceptance of freelancing as a staple of modern business, and you have a recipe for great opportunity in the design world for anyone who wants to build a career without getting tied to any given company for very long. That said, there are some challenges that must be acknowledged — and one of them is operational security.

Not only do designers need to keep their work (and their devices) secure to prevent the loss of vital files and data, but they also need to be able to assure their prospective clients that they can handle provided resources safely. In this post, we’re going to run through some core tips for how freelance designers can work safely. Let’s get started.

Use a strong VPN at all times

A VPN, or virtual private network, allows an internet user to cover and protect their internet activity by routing it through a proxy server that leads to a safe connection elsewhere. Anyone who attempts to hack their connection or just glean something from it — location, user identity, etc. — will simply run into the generic details of the VPN service.

VPNs have become very popular in recent years because they allow people to access region-locked content and services, but they’re also vital in business because a company can use a dedicated VPN to restrict access to its resources. A freelance designer not beholden to a particular client should use a VPN to shield their activity, and be aware that they may be asked to route their connection through a provided service when working with a cautious customer.

Store everything in the cloud

Storing work files locally is a recipe for disaster. Not only does it make it harder to work from multiple devices (a designer might want to use a laptop and a desktop, for instance), but it also puts important work at great risk. Computer hardware can fail, and laptops can be stolen. The one advantage of local storage — the exceptional security — isn’t worth the risks.

Cloud storage is the answer, making key files accessible from anywhere and ensuring that they’re safely backed up regardless of what happens with the devices used to work on them. It’s vital to choose the right cloud storage, though: free services such as Google Drive or Dropbox can be solid, but it’s worth moving up to a paid tier for the added space and protection.

Protect all vital work devices

Even when there’s no risk of data loss, work devices still need to be carefully protected. Laptops that are good for design work (having color-accurate displays and ideally touchscreens) tend to be fairly expensive, and while it’s certainly possible to get insurance to cover them, that won’t help much if there’s a client deadline to be hit shortly after such a device is stolen.

Strong authentication should absolutely be used, ensuring that no one who gains physical access to a work device can access the files: that means passwords that can’t realistically be guessed, and account recovery details that can’t be figured out through clever social engineering. Devices should also be protected from damage: a strong case can add a lot of irritating bulk, but it’s worth it to avoid a shattered display.

Maintain client boundaries

Lastly, when we talk about safety, something that needs to be considered is the maintenance of professional-client relations. Freelancing has many benefits, but one of the drawbacks is that a freelancer typically deals with a lot of different clients — and some of them will inevitably be difficult in some ways. This isn’t usually malicious, but it can be very inconvenient.

Among other things, maintaining boundaries means never giving a client unfettered access to personal storage. If work files must be monitored along the way (perhaps for accountability purposes), they should be kept in client storage. If a designer allows a client to access their cloud storage then falls out with them in some way, they’ll be in a vulnerable position — and it’s hardly unheard of for a petty professional dispute to descend into petty actions borne of spite.


It’s far from impossible to work safely as a modern freelance designer. Provided you take sensible precautions, you can work with minimal restrictions, changing your location and your clients as you prefer without putting your work at risk — but if you don’t take those precautions, you’ll be putting your livelihood and your reputation at risk. Take it seriously, do what needs to be done, and you’ll likely have no problems.

Feature image credit: https://www.piqsels.com/en/public-domain-photo-szepp

Categories: Others Tags:

Popular Design News of the Week: July 20, 2020 – July 26, 2020

July 26th, 2020 No comments

Every week users submit a lot of interesting stuff on our sister site Webdesigner News, highlighting great content from around the web that can be of interest to web designers.

The best way to keep track of all the great stories and news being posted is simply to check out the Webdesigner News site, however, in case you missed some here’s a quick and useful compilation of the most popular designer news that we curated from the past week.

4 Design Patterns that Violate “Back” Button Expectations

We Designed Something that Couldn’t Work

Appydev – Best Hand Picked Tools

Dark Mode for all the Things

15 Amazing Lego Re-Imagined Star Wars Scenes

37 SEO Tips all Web Designers Should Know

Medium is not the Home for your Ideas

How to Start Designing in Figma

Recursive Google Font

8 Popular UI Design Trends on Dribbble in 2020

Teenyicons — Tiny Minimal 1px Icons

Stripe: Building a Developer Cult

Fast Design will Kill your Product

How to Create a Strong Visual Identity for Digital Products

How to Create a Responsive Timeline with Image Thumbnails

Reploy – Instant, Shareable Previews of your Product

Wavium – Start a Blog and Newsletter in Under 5 Minutes

Can a Rebranding Make BMW Immune?

Webwaste

9 Epic Logo Designs Inspired by Famous Fictional Places

Create a Stunning Infographic with these 9 Tips and Tricks

How to Create a Web Design Portfolio with no Job Experience

5 Essential UX Design Principles that Great Designers Embrace

I Don’t Want to Be a Founder and I Don’t Think You do Either

Stop Worrying About People Stealing your Ideas

Want more? No problem! Keep track of top design news from around the web with Webdesigner News.

Source

Categories: Designing, Others Tags:

Apple declined to implement 16 Web APIs in Safari due to privacy concerns

July 24th, 2020 No comments

Why? Fingerprinting. Rather than these APIs being used for what they are meant for, they end up being used for gross ad tech. As in, “hey, we don’t know exactly who you are, but wait, through a script we can tell your phone stopped being idle from 8:00 am to 8:13 am and were near the Bluetooth device JBL BATHROOM, so it’s probably dad taking his morning poop! Let’s show him some ads for nicer speakers and flannel shirts ASAP.”

I’ll pull the complete list here from Catalin Cimpanu’s article:

  • Web Bluetooth – Allows websites to connect to nearby Bluetooth LE devices.
  • Web MIDI API – Allows websites to enumerate, manipulate and access MIDI devices.
  • Magnetometer API – Allows websites to access data about the local magnetic field around a user, as detected by the device’s primary magnetometer sensor.
  • Web NFC API – Allows websites to communicate with NFC tags through a device’s NFC reader.
  • Device Memory API – Allows websites to receive the approximate amount of device memory in gigabytes.
  • Network Information API – Provides information about the connection a device is using to communicate with the network and provides a means for scripts to be notified if the connection type changes
  • Battery Status API – Allows websites to receive information about the battery status of the hosting device.
  • Web Bluetooth Scanning – Allows websites to scan for nearby Bluetooth LE devices.
  • Ambient Light Sensor – Lets websites get the current light level or illuminance of the ambient light around the hosting device via the device’s native sensors.
  • HDCP Policy Check extension for EME – Allows websites to check for HDCP policies, used in media streaming/playback.
  • Proximity Sensor – Allows websites to retrieve data about the distance between a device and an object, as measured by a proximity sensor.
  • WebHID – Allows websites to retrieve information about locally connected Human Interface Device (HID) devices.
  • Serial API – Allows websites to write and read data from serial interfaces, used by devices such as microcontrollers, 3D printers, and othes.
  • Web USB – Lets websites communicate with devices via USB (Universal Serial Bus).
  • Geolocation Sensor (background geolocation) – A more modern version of the older Geolocation API that lets websites access geolocation data.
  • User Idle Detection – Lets website know when a user is idle.

I’m of mixing feelings. I do like the idea of the web being a competitive platform for building any sort of app and sometimes fancy APIs like this open those doors.

Not to mention that some of these APIs are designed to do responsible things, like knowing connections speeds through the Network Information API and sending less data if you can, and the same for the Battery Status API.

This is all a similar situation to :visited in CSS. Have you ever noticed how there are some CSS declarations you can’t use on visited links? JavaScript APIs will even literally lie about the current styling of visited links to make links always appear unvisited. Because fingerprinting.

Direct Link to ArticlePermalink


The post Apple declined to implement 16 Web APIs in Safari due to privacy concerns appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags:

A Font-Like SVG Icon System for Vue

July 24th, 2020 No comments

Managing a custom collection of icons in a Vue app can be challenging at times. An icon font is easy to use, but for customization, you have to rely on third-party font generators, and merge conflicts can be painful to resolve since fonts are binary files.

Using SVG files instead can eliminate those pain points, but how can we ensure they’re just as easy to use while also making it easy to add or remove icons?

Here is what my ideal icon system looks like:

  • To add icons, you just drop them into a designated icons folder. If you no longer need an icon, you simply delete it.
  • To use the rocket.svg icon in a template, the syntax is as simple as .
  • The icons can be scaled and colored using the CSS font-size and color properties (just like an icon font).
  • If multiple instances of the same icon appear on the page, the SVG code is not duplicated each time.
  • No webpack config editing is required.

This is what we will build by writing two small, single-file components. There are a few specific requirements for this implementation, though I’m sure many of you wizards out there could rework this system for other frameworks and build tools:

  • webpack: If you used the Vue CLI to scaffold your app, then you’re already using webpack.
  • svg-inline-loader: This allows us to load all of our SVG code and clean up portions we do not want. Go ahead and run npm install svg-inline-loader --save-dev from the terminal to get started.

The SVG sprite component

To meet our requirement of not repeating SVG code for each instance of an icon on the page, we need to build an SVG “sprite.” If you haven’t heard of an SVG sprite before, think of it as a hidden SVG that houses other SVGs. Anywhere we need to display an icon, we can copy it out of the sprite by referencing the id of the icon inside a tag like this:

<svg><use xlink:href="#rocket" /></svg>

That little bit of code is essentially how our component will work, but let’s go ahead create the component first. Here is the entire SvgSprite.vue file; some of it may seem daunting at first, but I will break it all down.

<!-- SvgSprite.vue -->

<template>
  <svg width="0" height="0" style="display: none;" v-html="$options.svgSprite" />
</template>

<script>
const svgContext = require.context(
  '!svg-inline-loader?' + 
  'removeTags=true' + // remove title tags, etc.
  '&removeSVGTagAttrs=true' + // enable removing attributes
  '&removingTagAttrs=fill' + // remove fill attributes
  '!@/assets/icons', // search this directory
  true, // search subdirectories
  /w+.svg$/i // only include SVG files
)
const symbols = svgContext.keys().map(path => {
  // get SVG file content
  const content = svgContext(path)
   // extract icon id from filename
  const id = path.replace(/^./(.*).w+$/, '$1')
  // replace svg tags with symbol tags and id attribute
  return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})
export default {
  name: 'SvgSprite',
  svgSprite: symbols.join('n'), // concatenate all symbols into $options.svgSprite
}
</script>

In the template, our lone element has its content bound to $options.svgSprite. In case you’re unfamiliar with $options it contains properties that are directly attached to our Vue component. We could have attached svgSprite to our component’s data, but we don’t really need Vue to set up reactivity for this since our SVG loader is only going to run when our app builds.

In our script, we use require.context to retrieve all of our SVG files and clean them up while we’re at it. We invoke svg-inline-loader and pass it several parameters using syntax that is very similar to query string parameters. I’ve broken these up into multiple lines to make them easier to understand.

const svgContext = require.context(
  '!svg-inline-loader?' + 
  'removeTags=true' + // remove title tags, etc.
  '&removeSVGTagAttrs=true' + // enable removing attributes
  '&removingTagAttrs=fill' + // remove fill attributes
  '!@/assets/icons', // search this directory
  true, // search subdirectories
  /w+.svg$/i // only include SVG files
)

What we’re basically doing here is cleaning up the SVG files that live in a specific directory (/assets/icons) so that they’re in good shape to use anywhere we need them.

The removeTags parameter strips out tags that we do not need for our icons, such as title and style. We especially want to remove title tags since those can cause unwanted tooltips. If you would like to preserve any hard-coded styling in your icons, then add removingTags=title as an additional parameter so that only title tags are removed.

We also tell our loader to remove fill attributes, so that we can set our own fill colors with CSS later. It’s possible you will want to retain your fill colors. If that’s the case, then simply remove the removeSVGTagAttrs and removingTagAttrs parameters.

The last loader parameter is the path to our SVG icon folder. We then provide require.context with two more parameters so that it searches subdirectories and only loads SVG files.

In order to nest all of our SVG elements inside our SVG sprite, we have to convert them from elements into SVG elements. This is as simple as changing the tag and giving each one a unique id, which we extract from the filename.

const symbols = svgContext.keys().map(path => {
  // extract icon id from filename
  const id = path.replace(/^./(.*).w+$/, '$1')
  // get SVG file content
  const content = svgContext(path)
  // replace svg tags with symbol tags and id attribute
  return content.replace('<svg', `<symbol id="${id}"`).replace('svg>', 'symbol>')
})

What do we do with this component? We place it on our page before any icons that depend on it. I recommend adding it to the top of the App.vue file.

<!-- App.vue -->
<template>
  <div id="app">
    <svg-sprite />
<!-- ... -->

The icon component

Now let’s build the SvgIcon.vue component.

<!-- SvgIcon.vue -->

<template>
  <svg class="icon" :class="{ 'icon-spin': spin }">
    <use :xlink:href="`#${icon}`" />
  </svg>
</template>

<script>
export default {
  name: 'SvgIcon',
  props: {
    icon: {
      type: String,
      required: true,
    },
    spin: {
      type: Boolean,
      default: false,
    },
  },
}
</script>

<style>
svg.icon {
  fill: currentColor;
  height: 1em;
  margin-bottom: 0.125em;
  vertical-align: middle;
  width: 1em;
}
svg.icon-spin {
  animation: icon-spin 2s infinite linear;
}
@keyframes icon-spin {
  from {
    transform: rotate(0deg);
  }
  to {
    transform: rotate(359deg);
  }
}
</style>

This component is much simpler. As previously mentioned, we leverage the tag to reference an id inside our sprite. That id comes from our component’s icon prop.

I’ve added a spin prop in there that toggles an .icon-spin class as an optional bit of animation, should we ever need. This could, for example, be useful for a loading spinner icon.

<svg-icon v-if="isLoading" icon="spinner" spin />

Depending on your needs, you may want to add additional props, such as rotate or flip. You could simply add the classes directly to the component without using props if you’d like.

Most of our component’s content is CSS. Other than the spinning animation, most of this is used to make our SVG icon act more like an icon font¹. To align the icons to the text baseline, I’ve found that applying vertical-align: middle, along with a bottom margin of 0.125em, works for most cases. We also set the fill attribute value to currentColor, which allows us to color the icon just like text.

<p style="font-size: 2em; color: red;">
  <svg-icon icon="exclamation-circle" /><!-- This icon will be 2em and red. -->
  Error!
</p>

That’s it! If you want to use the icon component anywhere in your app without having to import it into every component that needs it, be sure to register the component in your main.js file:

// main.js
import Vue from 'vue'
import SvgIcon from '@/components/SvgIcon.vue'
Vue.component('svg-icon', SvgIcon)
// ...

Final thoughts

Here are a few ideas for improvements, which I intentionally left out to keep this solution approachable:

  • Scale icons that have non-square dimensions to maintain their proportions
  • Inject the SVG sprite into the page without needing an additional component.
  • Make it work with vite, which is a new, fast (and webpack-free) build tool from Vue creator Evan You.
  • Leverage the Vue 3 Composition API.

If you want to quickly take these components for a spin, I’ve created a demo app based on the default vue-cli template. I hope this helps you develop an implementation that fits your app’s needs!


¹ If you’re wondering why we’re using SVG when we want it to behave like an icon font, then check out the classic post that pits the two against one another.


The post A Font-Like SVG Icon System for Vue appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags:

Holy Albatross with Widths

July 24th, 2020 No comments

Heydon’s Holy Albatross is a technique to have a row of elements break into a column of elements at a specific width. A specified parent width, not a screen width like a media query would have. So, like a container query (ya know, those things that don’t exist yet that we all want).

I’ve used it before, although it was pointed out to me that using it only on two elements isn’t really necessary, and that the Holy Albatross is most useful when working with three or more elements.

The original article kind didn’t get into setting the widths on the horizontal row of elements (say one of them needs to be larger than the others), but the follow-up article has a demo in it showing that flex-grow can be used to do exactly that. But Xiao Zhuo Jia notes that it’s not exactly a great system:

Problem is, it’s very difficult to set width to your non-stacked column, because the width is taken over by the hack.

One suggestion by Heydon is to use flex-grow, the problems are:

1. It is a very unintuitive way to set width – for a 3 column layout, you want 1 column to be 50% wide, you have to set flex-grow to 2.333
2. You have to know the number of total columns and set other columns’ flex-grow value accordingly

The other method is to use min-width and max-width, as shown by this codepen. I don’t believe max-width: 100% is needed as anything larger than 100% will be changed to 100% due to flex-shrink, so really we’re dealing with min-width.

The problem with this method is that we have to set min-width for all columns, or else flex-grow will take over and expand the column beyond the min-width we’ve set.

None of this is fun.

I poked around a bit and I found that you can have your cake and eat it too…

Xiao Zhuo Jia calls the Unholy Albatross. Check out the How does it work? part of the article to see the great CSS trickery. It has to do with using the max() function and CSS custom properties with fullbacks. It still feels very in the spirit of the Holy Albatross and allows you to set the width (via --width) on any given element with a pixel value or ratio. Plus, it supports gaps.

CodePen Embed Fallback

Direct Link to ArticlePermalink


The post Holy Albatross with Widths appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags:

Toyota’s New Logo 2020 | The Iconic Wordmark Was Dropped and Design Simplified

July 24th, 2020 No comments
Toyota new logo 2020

Wow, has 2020 been a rollercoaster.

For many, many reasons, but one in particular is that it seems to me that every single big player in the car industry game is redesigning their logo.

And they’re all revamping and going for a flat logo design.

First it was BMW, then VW, then just last week it was Nissan and I really thought we weren’t going to be hit with anymore major changes.

But now, we see Toyota coming in hot with a brand new logo design specifically for Europe.

And the people are torn.

Toyota’s New Logo Design

This is Toyota’s first redesign since 2009, and I’m personally happy to say that not only did they decide to drop the incredibly bright red color in the logo, they also decided to drop their famous wordmark.

And also, did you happen to notice how the top of the smaller, center ellipsis doesn’t connect to the top of the logo anymore?

Yes, it irks us as much as I’m sure it irks you too.

I have no idea why they chose to do that, and the internet is not very happy about it.

But I must say, I am in favor of the flat design and the monochrome color scheme they chose to go with in this new Toyota logo.

The red is not completely gone tho, as they use it in different parts of the marketing.

Which I’m honestly not opposed to. A little bit of color in a monochrome scheme can be an amazing idea.

toyota color scheme 2020

Another selling point that we love is the new sans serif that they came out with and they said that their aim is to be “approachable, human and highly technical, and it has been conscientiously engineered.”

This Sans Serif comes in four preferred and two optional weights, with each weight coming in upright and italics.

The Drop of The Wordmark

One of the most noticeable feature of all, besides the fact that they embraced a flat design and changed their color design, is that they dropped their iconic wordmark.

But, because Toyota’s logo is such a strong one, it can 100% stand alone and be recognized just as easily with or without the wordmark.

Personally, the more minimal, the better.

So, yes, dropping the wordmark was definitely a bold move, but a smart one.

In Conclusion

Here’s what Toyota had to say about their new logo and design.

“The aim of the redesign was to build Toyota’s image as a more progressive brand, while guaranteeing longevity in a digital world and ensuring that the brand continues to appeal to a modern and expanding customer base.

The direction for the new VI is shaped by four key goals: Forward-Thinking, More Premium feel, Consistent and Mobile-First.

The&Partnership’s design approach employed simplification, distilling Toyota’s key visual properties into a clean, considered system that better unifies the brand.

At its core is the new logo, which distils the brand’s emblem to a simplified 2-D design, losing the ‘Toyota’ wordmark; an acknowledgement of its status as one of the most recognisable brands in the world.

The new bespoke typography is Toyota Type, a sans serif font which enhances clarity and consistency across Toyota’s different business units.

Emphasising this is the primary colour palette, a clean, premium monochrome, with a red accent that provides a distinctive nod to Toyota.

The brand architecture has been streamlined and simplified through name changes, consistent typesetting and logo lock-ups.”

Toyota new logo 2020

All in all, I think that the update was desperately needed and it looks much, much cleaner and modern.

The only thing is that little elipisis that doesn’t touch the top of the logo.

Honestly, it bothers me more than I thought it would.

But other than that, a great update.

What do you guys think of this new logo and all of these new car logos in general?

Let us know in the comment section below!

Until next time,

Stay creative, folks!

Read More at Toyota’s New Logo 2020 | The Iconic Wordmark Was Dropped and Design Simplified

Categories: Designing, Others Tags:

WordPress.com Growth Summit

July 23rd, 2020 No comments

I’m speaking at The Official WordPress.com Growth Summit coming up in August. “Learn how to build and grow your site, from start to scale”, as they say. Lovely, thick, diverse set of speakers. It’s a little bit outside my normal spheres which makes it extra exciting for me. Selena Jackson:

The goal of this event is to inspire, connect you with the tools you need, and help you build your community. Sessions will take place across three tracks: blogging, business, and creative. You can take sessions on any or all tracks

If it interests you, it’s $79, and 20% off that with coupon code ChrisCoyier20.

My session?

CSS-Tricks: Putting WordPress to Work

Chris Coyier’s CSS-Tricks is a popular publication geared to web designers and developers. It’s also very much a business powered by WordPress. Chris will take us behind the scenes at CSS-Tricks, sharing all the ways it takes advantage of WordPress features, on both the technical and business sides.

Selena sent me some interesting questions as well:

What has kept you on WordPress for all these years? How has your website been essential to your growth or success?

It’s true that CSS-Tricks has never been anything but a WordPress site. I’ve never switched platforms or majorly re-architected in any way. But it’s not because of laziness or because I just don’t have any exposure to other methods of website building. I feel fortunate in that I’ve had lots of exposure and experience to different ways to build websites, from JAMstack with static site generators with cloud functions, to CMSs of all sorts, to Ruby on Rails sites, to Python-based sites… all kinds of stuff. All of it has a place.

Part of the equation is that I’m a solo developer for the most part on CSS-Tricks. Just me over here. I don’t have the budget for a fancy development team. But I still want to feel powerful and productive. That’s one of the things that WordPress has given to me. I feel like I can build just about anything on WordPress, and do it in a way that doesn’t feel like a mountain of technical debt that I would struggle to maintain.

Even though there is a decent amount of custom stuff going on, it probably looks like more than it is. Most of the work I do is pretty normcore WordPress development. I use popular well-maintained plugins. I use standard filters. I use the templating system as it was designed. I try to do things “The WordPress Way”, and that means year after year it’s very easy for me to maintain the site and build out what I want to build out. I never worry if I’m going against the grain or that I’m doing anything that puts me at any risk of not being able to upgrade things.

What’s one key thing you want our Growth Summit attendees to take away from your keynote talk/session?

I think my main vibe is going to be sharing just how powerful WordPress can be as a platform to run a publishing business on.

In a crowded and noisy web environment, what did you do to help your website stand out? What’s unique about your story or business?

What I hope we stand out for is the content on the site itself. We strive to be consistent, trustworthy, friendly, and helpful. In a world so laden with misinformation, zero-ethics advertising, and UX-hostile interfaces trying to squeeze everything they can from you, a site that’s just trying to help you understand the web and run a normal business out of it I hope feels as good to other people as it does to me.

Has COVID-19 changed how you use your website — or your approach to your online presence?

Not terribly. I’m finding advertisers pulling back a little bit, and keeping a closer eye on their sponsorship investments. And while I don’t love the idea of seeing those dollars go down, I don’t blame them. It’s smart for any business to make sure their money is well-spent.


The post WordPress.com Growth Summit appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags:

Accordion Rows in CSS Grid

July 23rd, 2020 No comments

I’d bet grid-template-columns is used about 10× more than grid-template-rows, but maybe everyone has just been missing out. Eric Meyer chucks a bunch of row lines onto his main site layout grid like this:

grid-template-rows: repeat(7, min-content) 1fr repeat(3, min-content);

That way, if you need to use them they are they for you:

I like this pattern. It feels good to me, having two sets of rows where the individual rows accordion open to accept content when needed, and collapse to zero height when not, with a “blank” row in between the sets that pushes them apart. It’s flexible, and even allows me to add more rows to the sets without having to rewrite all my layout styles.

Direct Link to ArticlePermalink


The post Accordion Rows in CSS Grid appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags:

Vue 3.0 has entered Release Candidate stage!

July 23rd, 2020 No comments

Vue is in the process of a complete overhaul that rebuilds the popular JavaScript framework from the ground up. This has been going on the last couple of years and, at long last, the API and implementation of Vue 3 core are now stabilize. This is exciting for a number of reasons:

  • Vue 3 promises to be much more performant than Vue 2.
  • Despite being a complete rewrite, the surface API hasn’t changed drastically, so there’s no need to forget what you already know about Vue.
  • The Vue documentation was completely migrated revised. (If you see me celebrating that, it’s because I helped work on it.)
  • There are several new features — like the Composition API (inspired by React Hooks) — that are additive and helpful for composition between multiple components.

Here’s how you can get your hands on the Vue 3 release candidate:

  • Play with it on CodePen, or include it with a script using https://unpkg.com/vue@next.
  • Start a project using Vite withnpm init vite-app hello-vue3. Vite comes with and support in single file components.
  • There is a Vue CLI pull request you can track.

There is more information about DevTools, experimental features and more in the release notes.

Docs updates, now in beta

Why am I so excited about the Vue doc updates? Because the updates include but are not limited to:

We are still actively working on things, of course, so if you see to-dos or unresolved work, please let us know! Feel free to open an issue or PR over at GitHub but, please, note that large requests will likely be closed out while we’re still refining our current work.

All in all we think you’ll enjoy it! It’s all the features you know and love, with some extra lovely bits, plus excellent performance.


The post Vue 3.0 has entered Release Candidate stage! appeared first on CSS-Tricks.

You can support CSS-Tricks by being an MVP Supporter.

Categories: Designing, Others Tags: